# EDEN — topologia de containers (docs/adr/0002-container-topology.md) # # Postgres isolado, credencial de banco só no serviço eden-api. Cada uma das # 3 aplicações web em container e porta próprios, falando só com a API via # HTTP — nunca direto com o banco. Rodar a partir da raiz do repositório: # # docker compose --env-file .env up -d # name: eden services: eden-postgres: image: postgres:18-alpine container_name: eden-postgres restart: unless-stopped environment: POSTGRES_DB: ${EDEN_DATABASE_NAME:-eden} POSTGRES_USER: ${EDEN_DATABASE_USER:-eden} POSTGRES_PASSWORD: ${EDEN_DATABASE_PASSWORD:?defina EDEN_DATABASE_PASSWORD no .env} volumes: # Postgres 18's official image switched to a pg_ctlcluster-style layout: # mount the parent dir, not .../data — see # https://github.com/docker-library/postgres/pull/1259 - eden_pgdata:/var/lib/postgresql # Mapeado ao host só para acesso de ferramenta local em desenvolvimento, # numa porta alta não-padrão — nunca 5432:5432, nunca exposto em produção # (ver docs/adr/0002-container-topology.md). Remover este bloco em prod. ports: - "${EDEN_POSTGRES_DEV_PORT:-55432}:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${EDEN_DATABASE_USER:-eden} -d ${EDEN_DATABASE_NAME:-eden}"] interval: 5s timeout: 5s retries: 10 networks: [eden_net] # S3-compatible object storage (packages/integrations/src/s3/client.ts) — # entra agora porque a geração de PDF (Documents/Quotes) é o primeiro # consumidor real de storage de objeto (mesma regra do Redis acima: # container próprio só quando o primeiro uso real precisar dele). Imagem # oficial `minio/minio` (a variante Bitnami parou de publicar tag # `latest` gratuita) — sem criação automática de bucket, por isso o # serviço `eden-minio-init` abaixo faz isso uma vez via `mc mb`. eden-minio: image: minio/minio:latest container_name: eden-minio restart: unless-stopped command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${S3_ACCESS_KEY_ID:?defina S3_ACCESS_KEY_ID no .env} MINIO_ROOT_PASSWORD: ${S3_SECRET_ACCESS_KEY:?defina S3_SECRET_ACCESS_KEY no .env} volumes: - eden_minio_data:/data # Console (9001) mapeado ao host só para inspeção manual em # desenvolvimento — mesmo racional do EDEN_POSTGRES_DEV_PORT acima. # Remover em produção. ports: - "9000:9000" - "9001:9001" healthcheck: test: ["CMD-SHELL", "curl -sf http://127.0.0.1:9000/minio/health/live || exit 1"] interval: 5s timeout: 5s retries: 10 networks: [eden_net] # One-shot: garante que o bucket usado por S3_BUCKET existe. Roda e sai # (`restart: "no"`) — eden-api só sobe depois dele terminar com sucesso. eden-minio-init: image: minio/mc:latest container_name: eden-minio-init restart: "no" depends_on: eden-minio: condition: service_healthy entrypoint: > sh -c " mc alias set local http://eden-minio:9000 ${S3_ACCESS_KEY_ID} ${S3_SECRET_ACCESS_KEY} && mc mb --ignore-existing local/${S3_BUCKET:-eden} " networks: [eden_net] eden-api: build: context: . dockerfile: infra/docker/Dockerfile.node args: APP_NAME: api container_name: eden-api restart: unless-stopped environment: DATABASE_URL: postgres://${EDEN_DATABASE_USER:-eden}:${EDEN_DATABASE_PASSWORD}@eden-postgres:5432/${EDEN_DATABASE_NAME:-eden} EDEN_API_PORT: ${EDEN_API_PORT:-8080} JWT_SIGNING_SECRET: ${JWT_SIGNING_SECRET} SIGNATURE_OTP_SECRET: ${SIGNATURE_OTP_SECRET} EDEN_FIELD_ENCRYPTION_KEY: ${EDEN_FIELD_ENCRYPTION_KEY} EDEN_SUPERADMIN_EMAIL: ${EDEN_SUPERADMIN_EMAIL} EDEN_SUPERADMIN_PASSWORD: ${EDEN_SUPERADMIN_PASSWORD} EDEN_CORS_ORIGINS: ${EDEN_CORS_ORIGINS} SMTP_HOST: ${SMTP_HOST} SMTP_PORT: ${SMTP_PORT} SMTP_USER: ${SMTP_USER} SMTP_PASS: ${SMTP_PASS} SMTP_FROM: ${SMTP_FROM} S3_ENDPOINT: ${S3_ENDPOINT} S3_REGION: ${S3_REGION} S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE} S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID} S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY} S3_BUCKET: ${S3_BUCKET} PUBLIC_BASE_URL: ${PUBLIC_BASE_URL} PDF_MAX_CONCURRENCY: ${PDF_MAX_CONCURRENCY:-3} PDF_RENDER_TIMEOUT_SECONDS: ${PDF_RENDER_TIMEOUT_SECONDS:-15} ports: - "${EDEN_API_PORT:-8080}:${EDEN_API_PORT:-8080}" depends_on: eden-postgres: condition: service_healthy eden-minio-init: condition: service_completed_successfully healthcheck: test: ["CMD-SHELL", "node -e \"fetch('http://localhost:'+ (process.env.EDEN_API_PORT||8080) +'/health/live').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""] interval: 10s timeout: 5s retries: 10 networks: [eden_net] eden-worker: build: context: . dockerfile: infra/docker/Dockerfile.node args: APP_NAME: worker container_name: eden-worker restart: unless-stopped environment: DATABASE_URL: postgres://${EDEN_DATABASE_USER:-eden}:${EDEN_DATABASE_PASSWORD}@eden-postgres:5432/${EDEN_DATABASE_NAME:-eden} depends_on: eden-postgres: condition: service_healthy networks: [eden_net] eden-core: build: context: . dockerfile: infra/docker/Dockerfile.web args: APP_NAME: core-web # Sempre relativo aqui (não ${VITE_API_BASE_URL} do .env — esse é # para `pnpm dev` local fora do Docker, valor diferente e não deve # vazar para este build arg). A API nunca tem domínio público # próprio; o proxy externo roteia este path para eden-api na rede # interna — ver comentário em .env.example. VITE_API_BASE_URL: /api/v1 container_name: eden-core restart: unless-stopped ports: - "${EDEN_CORE_PORT:-3001}:80" depends_on: eden-api: condition: service_healthy healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"] interval: 10s timeout: 5s retries: 5 networks: [eden_net] eden-parceiros: build: context: . dockerfile: infra/docker/Dockerfile.web args: APP_NAME: reseller-web # Sempre relativo aqui (não ${VITE_API_BASE_URL} do .env — esse é # para `pnpm dev` local fora do Docker, valor diferente e não deve # vazar para este build arg). A API nunca tem domínio público # próprio; o proxy externo roteia este path para eden-api na rede # interna — ver comentário em .env.example. VITE_API_BASE_URL: /api/v1 container_name: eden-parceiros restart: unless-stopped ports: - "${EDEN_PARCEIROS_PORT:-3002}:80" depends_on: eden-api: condition: service_healthy healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"] interval: 10s timeout: 5s retries: 5 networks: [eden_net] eden-assinante: build: context: . dockerfile: infra/docker/Dockerfile.web args: APP_NAME: subscriber-web # Sempre relativo aqui (não ${VITE_API_BASE_URL} do .env — esse é # para `pnpm dev` local fora do Docker, valor diferente e não deve # vazar para este build arg). A API nunca tem domínio público # próprio; o proxy externo roteia este path para eden-api na rede # interna — ver comentário em .env.example. VITE_API_BASE_URL: /api/v1 container_name: eden-assinante restart: unless-stopped ports: - "${EDEN_ASSINANTE_PORT:-3003}:80" depends_on: eden-api: condition: service_healthy healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"] interval: 10s timeout: 5s retries: 5 networks: [eden_net] networks: eden_net: driver: bridge volumes: eden_pgdata: eden_minio_data: