# ============================================================================ # Rastro — Docker Compose para o COOLIFY (máquina nova). # # Derivado do deploy/stack.yml (Swarm). Diferenças: # - Sem labels do Traefik: no Coolify você define os DOMÍNIOS pela UI (por serviço), # e o proxy do Coolify gera o roteamento. Ver deploy/MIGRACAO-COOLIFY.md. # - Sem rede `erico-net` externa: o Coolify põe todos os serviços do compose na # MESMA rede — eles se acham pelo nome do serviço (db, rabbitmq, minio, osrm…). # - `shm_size` no db (o Compose respeita, diferente do Swarm que exigia tmpfs). # - `deploy.resources.limits` mantidos (o `docker compose` do Coolify honra) — # ajuste conforme a capacidade da máquina nova. # - `migrate` roda só o SCHEMA (sem seed/bootstrap): os usuários/config vêm do # dump data-migration/rastro-userdata.sql (restaurado depois). Ver runbook. # - As imagens custom vêm do Gitea; as de infra, do Docker Hub. # # Variáveis ${...} → configure no ambiente do recurso no Coolify (ou .env). # ============================================================================ x-app-env: &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_EXCHANGE:-rastro.passagens} RASTRO_QUEUE: ${RASTRO_QUEUE:-rastro.passagens.processar} services: # ------------------------------------------------------------------ Infra db: image: timescale/timescaledb-ha:pg16 restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} shm_size: 1gb volumes: - db_data:/home/postgres/pgdata healthcheck: test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] interval: 10s timeout: 5s retries: 15 deploy: resources: limits: { cpus: '4.0', memory: 6G } rabbitmq: image: 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 deploy: resources: limits: { cpus: '1.0', memory: 640M } minio: image: minio/minio:latest 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 deploy: resources: limits: { cpus: '1.0', memory: 640M } gotenberg: image: gotenberg/gotenberg:8 restart: unless-stopped command: - gotenberg - --chromium-disable-javascript=false - --api-timeout=60s deploy: resources: limits: { cpus: '2.0', memory: 1536M } # OSRM (roteamento por vias) — OPCIONAL na 1ª subida. Precisa do grafo do Brasil # (~12-15GB) em OSRM_DATA_DIR. Sem ele, deixe COMENTADO e o app sobe sem rotas; # o processor/dfe-worker caem para no-op de roteamento. Habilite quando levar o grafo. # osrm: # image: osrm/osrm-backend:latest # restart: unless-stopped # command: ['osrm-routed', '--algorithm', 'mld', '--mmap', '--max-matching-size', '2000', '/data/brazil.osrm'] # volumes: # - ${OSRM_DATA_DIR:-/data/rastro/osrm}:/data:ro # deploy: # resources: # limits: { cpus: '2.0', memory: 6G } # ------------------------------------------------------------- Init (roda 1x) # SÓ SCHEMA (sem seed/bootstrap): os usuários/config vêm do dump. Roda uma vez e sai. migrate: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-node:${TAG:-latest} restart: "no" depends_on: db: condition: service_healthy environment: <<: *app-env NODE_ENV: production DB_CONNECTION_LIMIT: ${DB_CONNECTION_LIMIT_MIGRATE:-5} DB_POOL_TIMEOUT: ${DB_POOL_TIMEOUT:-15} 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" deploy: resources: limits: { cpus: '2.0', memory: 1536M } # ---------------------------------------------------------------- Drenagem # OBS: para migração inicial LEVE, deixe ingestor/dfe-worker parados (scale 0 no # Coolify) até querer puxar dados. O sistema serve os dados existentes sem eles. ingestor: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-node:${TAG:-latest} restart: unless-stopped depends_on: { db: { condition: service_healthy } } environment: <<: *app-env NODE_ENV: production DB_CONNECTION_LIMIT: ${DB_CONNECTION_LIMIT_INGESTOR:-5} DB_POOL_TIMEOUT: ${DB_POOL_TIMEOUT:-15} ONE_TP_AMB: ${ONE_TP_AMB:-1} ONE_VER_APLIC: ${ONE_VER_APLIC:-rastro-1.0} ONE_HOST: ${ONE_HOST:-one.svrs.rs.gov.br} ONE_TP_DIST: ${ONE_TP_DIST:-1,4} ONE_CUF: ${ONE_CUF:-41} ONE_CNPJ_OPER: ${ONE_CNPJ_OPER:-} ONE_POLL_INTERVAL: ${ONE_POLL_INTERVAL:-120} ONE_MAX_BATCHES: ${ONE_MAX_BATCHES:-15} ONE_BATCH_DELAY_MS: ${ONE_BATCH_DELAY_MS:-1200} ONE_IND_RESUMO: ${ONE_IND_RESUMO:-0} DFE_CERT_PFX_PATH: ${DFE_CERT_PFX_CONTAINER_PATH:-/certs/11de260603452f3c.pfx} DFE_CERT_PASSPHRASE: ${DFE_CERT_PASSPHRASE:-} CERT_DIR: /certs DFE_TLS_REJECT_UNAUTHORIZED: ${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: ${MINIO_BUCKET:-rastro-photos} command: sh -c "wait-for-db schema rabbitmq && pnpm --filter @rastro/ingestor start" volumes: - ${CERT_HOST_DIR:-/data/rastro/certificado}:/certs:ro deploy: resources: limits: { cpus: '1.0', memory: 640M } processor: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-node:${TAG:-latest} restart: unless-stopped depends_on: { db: { condition: service_healthy } } environment: <<: *app-env NODE_ENV: production DB_CONNECTION_LIMIT: ${DB_CONNECTION_LIMIT_PROCESSOR:-8} DB_POOL_TIMEOUT: ${DB_POOL_TIMEOUT:-15} SEFAZ_PR_DFE_URL: ${SEFAZ_PR_DFE_URL:-} SEFAZ_PR_DFE_USER: ${SEFAZ_PR_DFE_USER:-} SEFAZ_PR_DFE_PASSWORD: ${SEFAZ_PR_DFE_PASSWORD:-} OSRM_URL: ${OSRM_URL:-http://osrm:5000} REROUTE_STALE_INTERVAL_MIN: ${REROUTE_STALE_INTERVAL_MIN:-15} command: sh -c "wait-for-db schema rabbitmq && pnpm --filter @rastro/processor start" deploy: resources: limits: { cpus: '2.0', memory: 1G } dfe-worker: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-node:${TAG:-latest} restart: unless-stopped depends_on: { db: { condition: service_healthy } } environment: <<: *app-env NODE_ENV: production DB_CONNECTION_LIMIT: ${DB_CONNECTION_LIMIT_DFE_WORKER:-12} DB_POOL_TIMEOUT: ${DB_POOL_TIMEOUT:-15} SEFAZ_PR_DFE_URL: ${SEFAZ_PR_DFE_URL:-} SEFAZ_PR_DFE_USER: ${SEFAZ_PR_DFE_USER:-} SEFAZ_PR_DFE_PASSWORD: ${SEFAZ_PR_DFE_PASSWORD:-} OSRM_URL: ${OSRM_URL:-http://osrm:5000} DFE_GEN_INTERVAL: ${DFE_GEN_INTERVAL:-20} DFE_GEN_BATCH: ${DFE_GEN_BATCH:-25} DFE_GEN_CONCURRENCY: ${DFE_GEN_CONCURRENCY:-6} DFE_REFRESH_TTL_HOURS: ${DFE_REFRESH_TTL_HOURS:-12} DFE_GEN_ROUTE: ${DFE_GEN_ROUTE:-true} SEFAZ_WS_TP_AMB: ${SEFAZ_WS_TP_AMB:-1} SEFAZ_WS_ENDPOINTS: ${SEFAZ_WS_ENDPOINTS:-} SEFAZ_WS_MAX_CTE: ${SEFAZ_WS_MAX_CTE:-20} SEFAZ_ARQUIVOXML_ENDPOINTS: ${SEFAZ_ARQUIVOXML_ENDPOINTS:-} SEFAZ_ARQUIVOXML_CUF: ${SEFAZ_ARQUIVOXML_CUF:-41} SEFAZ_ARQUIVOXML_ORGAO: ${SEFAZ_ARQUIVOXML_ORGAO:-SEFA-PARANA} SEFAZ_ARQUIVOXML_SETOR: ${SEFAZ_ARQUIVOXML_SETOR:-AGAA} SEFAZ_ARQUIVOXML_VERAPLIC: ${SEFAZ_ARQUIVOXML_VERAPLIC:-PR_Rastro_v1} SEFAZ_ARQUIVOXML_MATR: ${SEFAZ_ARQUIVOXML_MATR:-999999} SEFAZ_ARQUIVOXML_MAX_NFE: ${SEFAZ_ARQUIVOXML_MAX_NFE:-40} SEFAZ_CONSULTADFE_ENDPOINTS: ${SEFAZ_CONSULTADFE_ENDPOINTS:-} DFE_CERT_PFX_PATH: ${DFE_CERT_PFX_CONTAINER_PATH:-/certs/11de260603452f3c.pfx} DFE_CERT_PASSPHRASE: ${DFE_CERT_PASSPHRASE:-} CERT_DIR: /certs DFE_TLS_REJECT_UNAUTHORIZED: ${DFE_TLS_REJECT_UNAUTHORIZED:-false} command: sh -c "wait-for-db schema && pnpm --filter @rastro/processor start:worker" volumes: - ${CERT_HOST_DIR:-/data/rastro/certificado}:/certs:ro deploy: resources: limits: { cpus: '2.0', memory: 1G } analytics: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-analytics:${TAG:-latest} restart: unless-stopped depends_on: { db: { condition: service_healthy } } environment: <<: *app-env INTEL_TOP_COMPANIES: ${INTEL_TOP_COMPANIES:-200} INTEL_TOP_COMMODITIES: ${INTEL_TOP_COMMODITIES:-50} INTEL_HORIZON_WEEKS: ${INTEL_HORIZON_WEEKS:-8} INTEL_CYCLE_HOURS: ${INTEL_CYCLE_HOURS:-24} deploy: resources: limits: { cpus: '2.0', memory: 1536M } # ---------------------------------------------------------------- Backend / Fronts # No Coolify, atribua os DOMÍNIOS a estes 3 serviços pela UI (ver runbook). O # backend precisa responder /api e /health NO MESMO domínio do web e do admin # (o front chama same-origin). Configure isso pelo roteamento por path do Coolify. backend: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-node:${TAG:-latest} restart: unless-stopped depends_on: { db: { condition: service_healthy } } environment: <<: *app-env NODE_ENV: production DB_CONNECTION_LIMIT: ${DB_CONNECTION_LIMIT_BACKEND:-12} DB_POOL_TIMEOUT: ${DB_POOL_TIMEOUT:-15} BACKEND_PORT: '39010' JWT_SECRET: ${JWT_SECRET} SESSION_COOKIE_NAME: ${SESSION_COOKIE_NAME:-rastro_session} CORS_ORIGINS: https://${WEB_DOMAIN},https://${ADMIN_DOMAIN} RATE_LIMIT_MAX: ${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: ${MINIO_BUCKET:-rastro-photos} command: sh -c "wait-for-db schema && pnpm --filter @rastro/backend start" # Porta interna do backend (aponte o domínio do Coolify para 39010, path /api e /health) expose: - "39010" deploy: resources: limits: { cpus: '2.0', memory: 1G } web: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-node:${TAG:-latest} restart: unless-stopped environment: <<: *app-env 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" deploy: resources: limits: { cpus: '1.0', memory: 640M } admin: image: ${REGISTRY:-gitea.interno.utic.app.br}/${NAMESPACE:-ericoalmeida}/rastro-node:${TAG:-latest} restart: unless-stopped environment: <<: *app-env 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" deploy: resources: limits: { cpus: '1.0', memory: 640M } volumes: db_data: rabbitmq_data: minio_data: