services: app: build: . # --proxy-headers + --forwarded-allow-ips make uvicorn honor the # X-Forwarded-Proto / X-Forwarded-Host headers any reverse proxy (Caddy, # nginx, Cloudflare Tunnel) sets. Without it, request.url_for() emits # http://localhost:8000/... even when the user is on https://, which # breaks OAuth callbacks (redirect_uri_mismatch). Belt-and-suspenders — # FORWARDED_ALLOW_IPS=* in .env does the same via env var. command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --proxy-headers --forwarded-allow-ips='*' ports: - "8000:8000" volumes: - data:/data - ./config:/app/config:ro # - ./custom-connectors:/app/connectors/custom:ro # Tier A: AI-generated connectors env_file: .env environment: - DATA_DIR=/data # Steer per-call tempdirs (Snowflake UNLOAD slice staging, # CSV→parquet intermediates) onto the data volume. The container # default ``/tmp`` lives on overlayfs (boot disk), which fills # under multi-GiB sliced exports — see connectors/keboola/ # storage_api.py:get_temp_root. Operators can override per # deployment via .env (or unset to fall back to system /tmp). - AGNES_TEMP_DIR=${AGNES_TEMP_DIR:-/data/tmp} healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:8000/api/health"] interval: 30s timeout: 5s retries: 3 restart: unless-stopped mem_limit: 4g mem_reservation: 1g cpus: 2.0 # One-shot: run extractor then rebuild orchestrator views extract: build: . command: > sh -c "python -m connectors.keboola.extractor && python -c 'from src.orchestrator import SyncOrchestrator; print(SyncOrchestrator().rebuild())'" volumes: - data:/data - ./config:/app/config:ro env_file: .env environment: - DATA_DIR=/data - AGNES_TEMP_DIR=${AGNES_TEMP_DIR:-/data/tmp} profiles: - extract scheduler: build: . command: python -m services.scheduler volumes: - data:/data - ./config:/app/config:ro env_file: .env environment: - DATA_DIR=/data - AGNES_TEMP_DIR=${AGNES_TEMP_DIR:-/data/tmp} - API_URL=http://app:8000 - SEED_ADMIN_EMAIL=${SEED_ADMIN_EMAIL:-} depends_on: app: condition: service_healthy restart: unless-stopped mem_limit: 2g cpus: 1.0 telegram-bot: build: . command: python -m services.telegram_bot volumes: - data:/data env_file: .env environment: - DATA_DIR=/data depends_on: - app profiles: - full restart: unless-stopped ws-gateway: build: . command: python -m services.ws_gateway volumes: - data:/data env_file: .env environment: - DATA_DIR=/data depends_on: - app profiles: - full restart: unless-stopped # NOTE: corporate-memory + session-collector previously ran here as # tight `restart: unless-stopped` boot loops behind `profiles: [full]`. # As of #176 the scheduler container drives both through admin HTTP # endpoints (/api/admin/run-corporate-memory, # /api/admin/run-session-collector). The verification-detector job # was never in compose; it now ships the same way. The app remains # the sole writer to system.duckdb. Operators previously running # COMPOSE_PROFILES=full need to drop those service stanzas from any # custom Compose overrides. # TLS reverse proxy. Corporate-CA certs mounted from /data/state/certs # (managed by scripts/ops/agnes-tls-rotate.sh on the VM). For local # development without certs, run without --profile tls and hit :8000 # directly. caddy: image: caddy:2-alpine ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - /data/state/certs:/certs:ro - caddy_data:/data - caddy_config:/config # Read-only mount of the agnes data dir so Caddy's file_server can # serve parquets directly (sendfile/zero-copy) and bypass the app's # uvicorn workers — see Caddyfile's @download handler. Mounted at # /srv (not /data) because /data is already the caddy_data volume. - data:/srv:ro environment: - DOMAIN=${DOMAIN:-localhost} # Passes through whatever the operator set in .env. Caddyfile uses # {$CADDY_TLS:tls /certs/fullchain.pem /certs/privkey.pem} so: # - unset → cert-file mode (corp PKI rotated by tls-rotate.sh) # - "tls " → Let's Encrypt auto-issue # - "tls internal" → Caddy-managed self-signed - CADDY_TLS depends_on: app: condition: service_healthy restart: unless-stopped profiles: - tls volumes: data: caddy_data: caddy_config: