Reference compose file
Your Agent Service distribution ships a compose file for application containers only (often nameddocker-compose-no-storage.yml), colocated with startup/ and your team’s setup runbooks.
That layout runs application containers only. It does not start Postgres or Redis; you must provide DB_HOST / DB_* and REDIS_URL (or equivalent) via .env.fastapi_main and run databases elsewhere (RDS, ElastiCache, self-managed, etc.), consistent with the comments at the top of the compose file.
Hardware
Plan for at least 4 GB RAM on the Docker host for a minimal dev-style stack that includes Traefik, two API replicas (api-blue / api-green), queue_runner, and webhooks. Production sizing depends on traffic, model latency, and worker concurrency—scale CPU/RAM accordingly.
You must host Postgres and Redis yourself (the compose bundle does not start them). Postgres must be PostgreSQL 17–compatible (version and extensions aligned with the migrations and client libraries in your backend release). Redis must be reachable at whatever URL you set as REDIS_URL—typically the same class of network as the API (VPC, TLS to a managed cache, etc.). For both stores, prefer managed or otherwise operationally elastic services (scaling storage, instance size, replicas) so traffic spikes and growth do not leave you chasing capacity and throughput on a single undersized host.
Services in this stack
| Service | Role |
|---|---|
| traefik | Reverse proxy / router on host port 8001; connects to api-blue and api-green on the app network. Requires Traefik v3.6+ for modern Docker API compatibility (see file header comments). |
| api-blue / api-green | FastAPI app (uvicorn fastapi_main:app, port 8001 in-container). Blue/green traffic split via Traefik priorities. Mounts ./keys read-only. |
| queue_runner | Background worker process (queue_runner.py) using the same image and env. |
| webhooks | Webhook delivery worker (agent_event_webhooks.py). |
psql-migrate (profile migrate) | One-shot Postgres migration runner; not started by default. |
app (explicit name app). Project name api to avoid colliding with other stacks.
Configuration
- Generate env and keys (first-time or rotation). From the application repository root, run
startup/generate_env.pyso you get akeys/directory and.env.fastapi_mainwith generated secrets and DB passwords (see the script’s docstring forOUTPUT_DIR/ positional output path and--db-hostoverrides). Your internal README may wrap this step; run it before hand-editing compose-only hosts if you want a sane baseline. - Place
.env.fastapi_mainandkeys/where your compose file expects them (usually the same working directory you start compose from). - Set
DB_*,REDIS_URL, JWT/signing variables, and any cloud credentials your backend expects—merging or overriding whatgenerate_envproduced if your databases live outside localhost. - Start from that directory:
- Point Python
AsapiClientor the CLIconfig --hostat your Traefik ingress (for examplehttps://your-bastion-host:8001or TLS-terminated URL).
Zero-downtime rotation (blue / green)
On a single host,docker-compose-no-storage.yml runs two API containers—api-blue and api-green—behind Traefik on port 8001. Each slot has a Traefik router priority; default traffic goes to whichever slot has the higher priority (compose defaults are roughly blue = 100, green = 1, so blue is live first).
Use this pattern to rotate in a new build without blanking the only API instance:
- Build or pull the new image and point
IMAGE_TAG(or your compose image override) at it—often while the current live slot keeps serving traffic. - Recreate the standby slot with the new image (for example
docker compose ... up -d api-greenafter updating the green service) so it passes its health check before you cut over. - Smoke-test the standby from the host or bastion. The compose layout supports a pinned route via header
X-Hit-Service: blueorX-Hit-Service: greenso you can hit/health(or a narrow API check) against one slot only. Treat that header as diagnostic tooling, not a security boundary—it is trivially spoofable, so rely on it only from localhost or a trusted admin path. - Swap priorities by setting
API_BLUE_PRIORITYandAPI_GREEN_PRIORITYso the slot you just validated becomes the winner. These variables are read from a.envnext to the compose file (Compose label interpolation), not from.env.fastapi_main—see the comments in your shipped compose file for the exact mechanism. - Apply the change:
docker compose -f docker-compose-no-storage.yml up -d api-blue api-green(and restartqueue_runner/webhookswhen they share the same image tag, so workers match the API version you intend to run).
migrate profile in the compose file) before shifting default traffic to a code version that depends on the new schema.
Tenant bootstrap (create_tenant)
After the database exists and migrations have been applied, create an initial tenant and user with the repo script (run from the application project root with .env.fastapi_main loaded):
startup/create_tenant.py in the monorepo for API key issuance, service account registration, and other flags (--get-api-key, --register-service-account, etc.). Your internal DEVELOPER-README / runbooks describe the full first-time setup flow.