Files
rastro-deploy/docker-compose.yml
T

309 lines
13 KiB
YAML

# ============================================================================
# Rastro — Compose "INTERNET" (servidor com acesso externo, ex.: Coolify repr-externo).
# Espelho do que RODA no Coolify (stack rastro-br) — copiar/colar no Edit Compose File.
# • Infra (db/rabbitmq/minio/gotenberg): ESPELHADA no Gitea — pull do Docker Hub neste servidor
# corrompia camadas grandes ("magic number mismatch") e a minio/minio saiu do Docker Hub.
# • App (rastro-node/analytics): do Gitea EXTERNO (pacotes públicos, pull anônimo).
# • Roteamento do /api via LABELS Traefik no backend (NÃO usar o campo Domains do
# backend no Coolify — o Domains-com-path corta o /api; o backend registra as
# rotas COM /api, então precisa chegar sem strip). web/admin ficam no Domains do Coolify.
# • Volume do banco: dbdata2 (o schema já subiu nele; renomear zera o banco).
# • Teradata SEFAZ (processor, backend, analytics): nós COP por IP — o DNS da UTIC não
# resolve trdtdbcopN. TERADATA_ENABLED/USER/PASSWORD e EVIDENCE_KEYS em Environment
# Variables do Coolify. EVIDENCE_KEYS: "1:<openssl rand -base64 32>" (guarde cópia!).
# • Certificado A1 (ingestor, dfe-worker, backend): DFE_CERT_PFX_BASE64 + DFE_CERT_PASSPHRASE
# como variáveis secretas no Coolify — sem montar arquivo no servidor.
# • Drenagem: ingestor (ONE) + dfe-worker (cadeia) + osrm. O osrm baixa o grafo (15 GB) 1 vez
# de OSRM_DATA_URL para o volume osrm_data; processor/dfe-worker roteiam por OSRM_URL
# (http://osrm:5000 quando o grafo estiver pronto).
# ============================================================================
x-app-env:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
services:
db:
image: 'gitea.externo.utic.app.br/ericoalmeida/timescaledb-ha:pg16'
restart: unless-stopped
environment:
POSTGRES_USER: '${POSTGRES_USER}'
POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}'
POSTGRES_DB: '${POSTGRES_DB}'
shm_size: 1gb
volumes:
- 'dbdata2:/home/postgres/pgdata'
healthcheck:
test:
- CMD-SHELL
- 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'
interval: 10s
timeout: 5s
retries: 15
rabbitmq:
image: 'gitea.externo.utic.app.br/ericoalmeida/rabbitmq:3-management'
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_USER: '${RABBITMQ_DEFAULT_USER}'
RABBITMQ_DEFAULT_PASS: '${RABBITMQ_DEFAULT_PASS}'
volumes:
- 'rabbitmq_data:/var/lib/rabbitmq'
healthcheck:
test:
- CMD
- rabbitmq-diagnostics
- '-q'
- ping
interval: 15s
timeout: 5s
retries: 10
minio:
# Espelho da imagem oficial (quay.io; o Docker Hub não publica mais a MinIO), versão fixa.
image: 'gitea.externo.utic.app.br/ericoalmeida/minio:RELEASE.2025-09-07T16-13-09Z'
restart: unless-stopped
command: 'server /data --console-address ":9001"'
environment:
MINIO_ROOT_USER: '${MINIO_ROOT_USER}'
MINIO_ROOT_PASSWORD: '${MINIO_ROOT_PASSWORD}'
volumes:
- 'minio_data:/data'
gotenberg:
image: 'gitea.externo.utic.app.br/ericoalmeida/gotenberg:8'
restart: unless-stopped
command:
- gotenberg
- '--chromium-disable-javascript=false'
- '--api-timeout=60s'
osrm:
# Rotas pelas vias (grafo do Brasil, MLD, v5.26.0). Na 1ª subida BAIXA o grafo
# (15 GB) de OSRM_DATA_URL para o volume osrm_data (retoma e confere sha256); nas
# seguintes só roteia. Serviço de longa duração: o deploy não fica esperando.
# --mmap: ~70-700 MB de RAM. Precisa de ~25 GB livres no disco na 1ª subida.
# Enquanto baixa, OSRM_URL pode continuar apontando para o OSRM remoto; quando o log
# mostrar "grafo OSRM pronto", troque OSRM_URL para http://osrm:5000 e faça Redeploy.
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-osrm:v1'
restart: unless-stopped
environment:
OSRM_DATA_URL: '${OSRM_DATA_URL}'
volumes:
- 'osrm_data:/data'
expose:
- '5000'
ingestor:
# DRENAGEM do ONE (passagens + fotos). Valores de cadência da produção antiga.
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-node:v3.3'
restart: unless-stopped
depends_on:
db:
condition: service_healthy
rabbitmq:
condition: service_healthy
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
NODE_ENV: production
DB_CONNECTION_LIMIT: '5'
ONE_TP_AMB: '1'
ONE_VER_APLIC: rastro-1.0
ONE_HOST: one.svrs.rs.gov.br
ONE_TIMEOUT_MS: '30000'
ONE_TP_DIST: '1,4'
ONE_CUF: '41'
ONE_CNPJ_OPER: ''
ONE_POLL_INTERVAL: '30'
ONE_MAX_BATCHES: '40'
ONE_BATCH_DELAY_MS: '700'
ONE_IND_RESUMO: '0'
# Certificado A1 por variável secreta (base64 do .pfx) — sem montar arquivo.
DFE_CERT_PFX_BASE64: '${DFE_CERT_PFX_BASE64}'
DFE_CERT_PASSPHRASE: '${DFE_CERT_PASSPHRASE}'
DFE_TLS_REJECT_UNAUTHORIZED: 'false'
MINIO_ENDPOINT: minio
MINIO_PORT: '9000'
MINIO_ROOT_USER: '${MINIO_ROOT_USER}'
MINIO_ROOT_PASSWORD: '${MINIO_ROOT_PASSWORD}'
MINIO_BUCKET: rastro-photos
command: 'sh -c "wait-for-db schema rabbitmq && pnpm --filter @rastro/ingestor start"'
migrate:
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-node:v3.3'
restart: 'no'
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
NODE_ENV: production
DB_CONNECTION_LIMIT: '5'
command: 'sh -c "wait-for-db && pnpm --filter @rastro/db migrate && pnpm --filter @rastro/db postgis && pnpm --filter @rastro/db timescale && pnpm --filter @rastro/db intel && pnpm --filter @rastro/db analyze && pnpm --filter @rastro/db restore-users"'
processor:
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-node:v3.3'
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
NODE_ENV: production
DB_CONNECTION_LIMIT: '8'
OSRM_URL: '${OSRM_URL}'
REROUTE_STALE_INTERVAL_MIN: '15'
TERADATA_ENABLED: '${TERADATA_ENABLED:-false}'
TERADATA_HOSTS: '10.14.203.21,10.14.203.22,10.14.203.23'
TERADATA_USER: '${TERADATA_USER}'
TERADATA_PASSWORD: '${TERADATA_PASSWORD}'
TERADATA_DATABASE: P_ACCDB
DFE_HOT_DAYS: '7'
DFE_TD_GRACE_DAYS: '10'
DFE_OPEN_MAX_DAYS: '60'
DFE_ROUTE_RETENTION_DAYS: '90'
DFE_META_RETENTION_DAYS: '400'
DFE_LIFECYCLE_ENABLED: 'true'
command: 'sh -c "wait-for-db schema rabbitmq && pnpm --filter @rastro/processor start"'
dfe-worker:
# Cadeia MDF-e → CT-e → NF-e (SEFAZ; Teradata para transporte além de D+1) + rota OSRM.
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-node:v3.3'
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
NODE_ENV: production
DB_CONNECTION_LIMIT: '12'
OSRM_URL: '${OSRM_URL}'
DFE_GEN_INTERVAL: '15'
DFE_GEN_BATCH: '6'
DFE_GEN_CONCURRENCY: '6'
DFE_REFRESH_TTL_HOURS: '12'
DFE_GEN_ROUTE: 'true'
SEFAZ_WS_TP_AMB: '1'
SEFAZ_WS_MAX_CTE: '20'
SEFAZ_ARQUIVOXML_CUF: '41'
SEFAZ_ARQUIVOXML_ORGAO: SEFA-PARANA
SEFAZ_ARQUIVOXML_SETOR: AGAA
SEFAZ_ARQUIVOXML_VERAPLIC: PR_Rastro_v1
SEFAZ_ARQUIVOXML_MATR: '999999'
SEFAZ_ARQUIVOXML_MAX_NFE: '40'
# Certificado A1 por variável secreta (base64 do .pfx) — sem montar arquivo.
DFE_CERT_PFX_BASE64: '${DFE_CERT_PFX_BASE64}'
DFE_CERT_PASSPHRASE: '${DFE_CERT_PASSPHRASE}'
DFE_TLS_REJECT_UNAUTHORIZED: 'false'
TERADATA_ENABLED: '${TERADATA_ENABLED:-false}'
TERADATA_HOSTS: '10.14.203.21,10.14.203.22,10.14.203.23'
TERADATA_USER: '${TERADATA_USER}'
TERADATA_PASSWORD: '${TERADATA_PASSWORD}'
TERADATA_DATABASE: P_ACCDB
DFE_HOT_DAYS: '7'
DFE_OPEN_MAX_DAYS: '60'
TD_CHAIN_MIN_AGE_HOURS: '36'
# wait-for-db osrm: sem OSRM roteando, a rota seria gravada como RETA (fallback).
command: 'sh -c "wait-for-db schema osrm && pnpm --filter @rastro/processor start:worker"'
analytics:
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-analytics:v4'
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
INTEL_TOP_COMPANIES: '200'
INTEL_TOP_COMMODITIES: '50'
INTEL_HORIZON_WEEKS: '8'
INTEL_CYCLE_HOURS: '24'
TERADATA_ENABLED: '${TERADATA_ENABLED:-false}'
TERADATA_HOSTS: '10.14.203.21,10.14.203.22,10.14.203.23'
TERADATA_USER: '${TERADATA_USER}'
TERADATA_PASSWORD: '${TERADATA_PASSWORD}'
backend:
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-node:v3.3'
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
NODE_ENV: production
DB_CONNECTION_LIMIT: '12'
BACKEND_PORT: '39010'
JWT_SECRET: '${JWT_SECRET}'
SESSION_COOKIE_NAME: rastro_session
CORS_ORIGINS: 'https://${WEB_DOMAIN},https://${ADMIN_DOMAIN}'
RATE_LIMIT_MAX: '1000'
GOTENBERG_URL: 'http://gotenberg:3000'
MINIO_ENDPOINT: minio
MINIO_PORT: '9000'
MINIO_ROOT_USER: '${MINIO_ROOT_USER}'
MINIO_ROOT_PASSWORD: '${MINIO_ROOT_PASSWORD}'
MINIO_BUCKET: rastro-photos
TERADATA_ENABLED: '${TERADATA_ENABLED:-false}'
TERADATA_HOSTS: '10.14.203.21,10.14.203.22,10.14.203.23'
TERADATA_USER: '${TERADATA_USER}'
TERADATA_PASSWORD: '${TERADATA_PASSWORD}'
TERADATA_DATABASE: P_ACCDB
TD_DOC_CACHE_HOURS: '24'
EVIDENCE_KEYS: '${EVIDENCE_KEYS}'
EVIDENCE_BUCKET: rastro-evidence
# Certificado A1 para o XML sob demanda: o .pfx em base64 numa variável secreta
# (base64 -i certificado.pfx | tr -d '\n'). Vazio = XML sob demanda desligado.
DFE_CERT_PFX_BASE64: '${DFE_CERT_PFX_BASE64}'
DFE_CERT_PASSPHRASE: '${DFE_CERT_PASSPHRASE}'
DFE_TLS_REJECT_UNAUTHORIZED: 'false'
command: 'sh -c "wait-for-db schema && pnpm --filter @rastro/backend start"'
expose:
- '39010'
labels:
- traefik.enable=true
- 'traefik.http.routers.rastroapi.rule=(Host(`rastrobr.externo.utic.app.br`) || Host(`admin.rastrobr.externo.utic.app.br`)) && PathPrefix(`/api`)'
- traefik.http.routers.rastroapi.entryPoints=https
- traefik.http.routers.rastroapi.tls=true
- traefik.http.routers.rastroapi.priority=1000
- traefik.http.services.rastroapi.loadbalancer.server.port=39010
web:
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-node:v3.3'
restart: unless-stopped
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
VITE_DEV_HELPERS: 'false'
PREVIEW_ALLOWED_HOSTS: '${WEB_DOMAIN}'
command: 'sh -c "pnpm --filter web preview --host 0.0.0.0 --port 39000"'
expose:
- '39000'
admin:
image: 'gitea.externo.utic.app.br/ericoalmeida/rastro-node:v3.3'
restart: unless-stopped
environment:
DATABASE_URL: 'postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public'
RABBITMQ_URL: 'amqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@rabbitmq:5672'
RASTRO_EXCHANGE: rastro.passagens
RASTRO_QUEUE: rastro.passagens.processar
VITE_DEV_HELPERS: 'false'
PREVIEW_ALLOWED_HOSTS: '${ADMIN_DOMAIN}'
command: 'sh -c "pnpm --filter admin preview --host 0.0.0.0 --port 39020"'
expose:
- '39020'
volumes:
osrm_data: null
dbdata2: null
rabbitmq_data: null
minio_data: null