| web | Build from repo root Dockerfile | 5050 | Flask app: webhooks, API, test routes. Depends on db and redis. Mounts .:/app and ./backups:/backups. |
| db | postgres:15 | 5432 (internal) | PostgreSQL. Data in volume postgres_data. Env: POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB. |
| redis | redis:7 | 6379 | Redis: Celery broker and result backend. Healthcheck: redis-cli ping. Volume redis_data. |
| celery | Same build as web | — | Celery worker: celery -A tasks.celery worker. Depends on redis; uses same DATABASE_URL and CELERY_* as web. Mounts app and backups. |
| beat | Same build as web | — | Celery Beat: celery -A tasks.celery beat with schedule in /tmp. Depends on redis. Volume beat_schedule for schedule persistence. |
| admin-portal | Build from admin_portal/Dockerfile | 5002 | Admin dashboard (Flask + Next.js). Depends on db; mounts ./backups for log viewer. |
| docs | Mintlify static build | 5003 | Documentation site (Mintlify). Serves static build. |
| caddy | caddy:2 | 80, 443 | Reverse proxy. Mounts ./caddy/Caddyfile. Depends on web and docs (admin-portal can be added). Proxies api/admin/docs subdomains. |
| ngrok | ngrok/ngrok:3 | 4040 (dashboard) | Dev only (profiles: [dev]). Exposes web (5050) via public HTTPS. Mounts ./ngrok_config/ngrok.yml. |