Ctrl K
ComunidadGuías…

docker-compose.prod.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Production overlay — adds TLS (Caddy auto-HTTPS), enforces auth, and sets restart policies.
# Layer it on top of the base compose:
#
#   DOMAIN=app.example.com docker compose -f docker-compose.yml -f docker-compose.prod.yml \
#       --profile full up -d --build
#
# Requires: a VM with Docker, a DNS A record for $DOMAIN pointing at it, and ports 80+443 open.
# Lock the rest down with a firewall (e.g. `ufw allow 80,443/tcp` and deny 8000/8080/5432/9000).

# Fail-closed datastore credentials, shared by every service that opens the database or the object
# store. This is an anchor rather than two copies because the API and the job worker MUST fail closed
# in the same way: the worker inherits the base compose's permissive `${POSTGRES_PASSWORD:-bim}`
# unless something overrides it here, so a copy that drifted would leave the worker connecting with
# the dev default password in production while the API refused to start — and only the API's refusal
# would be visible.
x-prod-datastore: &prod-datastore
  DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER:-bim}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env for production}@postgres:5432/${POSTGRES_DB:-bim}"
  S3_SECRET_KEY: "${S3_SECRET_KEY:?set S3_SECRET_KEY in .env for production}"

services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports: ["80:80", "443:443"]
    environment:
      DOMAIN: "${DOMAIN:?set DOMAIN to your public hostname}"
    volumes:
      - ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    depends_on: [web]
    profiles: ["full"]

  web:
    restart: unless-stopped

  # Shared store for per-IP rate limiting + login-lockout counters across the API's uvicorn workers
  # (the base stack runs UVICORN_WORKERS=4; without Redis those counters are per-process).
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--save", "", "--appendonly", "no"]   # ephemeral counters; no persistence
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
    profiles: ["full"]

  api:
    restart: unless-stopped
    depends_on:
      redis: { condition: service_healthy }
    environment:
      AEC_RBAC: "1"                                     # enforce auth/roles in production (override the dev default)
      AEC_REQUIRE_SECRET: "1"                           # refuse to start without AEC_AUTH_SECRET (fail-closed)
      AEC_HSTS: "1"                                     # served over HTTPS via Caddy → enable HSTS
      AEC_COOKIE_SECURE: "1"                            # force the Secure flag on the auth cookie
      AEC_CSP: "${AEC_CSP:-1}"                          # strict resource Content-Security-Policy
      AEC_SIGNED_URL_TTL: "${AEC_SIGNED_URL_TTL:-3600}"
      AEC_MAX_UPLOAD_MB: "${AEC_MAX_UPLOAD_MB:-1024}"
      AEC_RATE_LIMIT_RPM: "${AEC_RATE_LIMIT_RPM:-600}"  # per-IP throttle (shared via Redis below)
      AEC_REDIS_URL: "redis://redis:6379/0"             # shared rate-limit + login-lockout across workers
      AEC_ADMIN_EMAILS: "${AEC_ADMIN_EMAILS:-}"         # platform admins (Settings/audit/user mgmt)
      # Host-header pinning (opt-in): e.g. "app.example.com,localhost" — include localhost so the
      # container healthcheck and the internal nginx proxy keep working. Empty = no restriction.
      AEC_ALLOWED_HOSTS: "${AEC_ALLOWED_HOSTS:-}"
      # Fail-closed on the datastore secrets in production — refuse to start rather than silently fall
      # back to the dev defaults (the base compose uses `${…:-bim}` / `${…:-minioadmin}` for local dev).
      <<: *prod-datastore
    # Resource limits (opt-in): same env vars as the base compose (see .env.example). Kept OFF by
    # default so a too-low cap can't OOM-kill a legitimate large-IFC reconvert. Uncomment + size
    # for the production host.
    # deploy:
    #   resources:
    #     limits:
    #       memory: "${AEC_API_MEM_LIMIT:-4g}"
    #       cpus: "${AEC_API_CPUS:-2}"

  # JOB-WORKER-SPLIT — the queue container from the base compose, made production-shaped. The base
  # file already sets `AEC_JOB_WORKER=off` on the API and defines this service; all that is added here
  # is the restart policy and the fail-closed credentials.
  #
  # This is the service to scale when conversions back up — `--scale worker=N` — and the one to give
  # the memory limit to, because after the split it is where the large IFC parses actually happen.
  worker:
    restart: unless-stopped
    healthcheck: { disable: true }   # inherited from the API image; this service binds no port
    environment:
      <<: *prod-datastore
      AEC_JOB_WORKER: "inline"       # this process IS the worker
    # deploy:                        # opt-in cap — the heavy parses live here now, so size this one
    #   resources:                   # before the API's.
    #     limits:
    #       memory: "${AEC_WORKER_MEM_LIMIT:-4g}"
    #       cpus: "${AEC_WORKER_CPUS:-2}"

  postgres:
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env for production}"
    # deploy:                              # opt-in cap — uncomment + size for your host
    #   resources:
    #     limits:
    #       memory: "${AEC_PG_MEM_LIMIT:-2g}"

  minio:
    restart: unless-stopped
    environment:
      MINIO_ROOT_PASSWORD: "${S3_SECRET_KEY:?set S3_SECRET_KEY in .env for production}"
    # deploy:                              # opt-in cap — uncomment + size for your host
    #   resources:
    #     limits:
    #       memory: "${AEC_MINIO_MEM_LIMIT:-1g}"

volumes:
  caddy-data:
  caddy-config: