Follow-up from #108 review. Today `docker-compose.yaml` is used for both the local-dev experience and as the production-shape reference. The current file mixes:
- Production-shape services (`postgres`, `e2a`, `web`, `mcp`)
- Local-dev-only services (`mailpit`)
- Local-dev-only env vars (`E2A_OUTBOUND_SMTP_HOST: "mailpit"`, `E2A_USAGE_TRACKING: "true"`, demo HMAC secret, demo shared domain `agents.localhost`, etc.)
If someone runs this compose file in production by accident, outbound mail silently funnels to the dev catch-all. The commit message and CLAUDE.md both say "drop the mailpit service in production," but the file itself doesn't enforce it.
Proposed split
Standard Docker Compose convention: base file + override file. Compose auto-merges `compose.yaml` + `compose.override.yaml` when both are present.
`compose.yaml` (production-shape, runs anywhere):
- `postgres`, `e2a`, `web`, `mcp` services
- Empty / production-safe env defaults (no demo HMAC, real `E2A_SHARED_DOMAIN` from env, etc.)
- No port bindings to `127.0.0.1:` so production reverse proxy can wire its own networking
`compose.dev.yaml` (local dev override, gitignored copy or checked in as `compose.dev.example.yaml`):
- `mailpit` service (with its volume + healthcheck)
- e2a service additions: `depends_on: mailpit`, `E2A_OUTBOUND_SMTP_HOST: "mailpit"`, demo HMAC, demo shared domain, `E2A_USAGE_TRACKING: "true"`
- web service build args (`NEXT_PUBLIC_AGENTS_DOMAIN: agents.localhost`)
- Loopback port bindings for local convenience
Then:
- Local dev: `docker compose up` (Compose auto-includes `compose.override.yaml` if it's named that, or `docker compose -f compose.yaml -f compose.dev.yaml up` for an explicit dev file).
- Production: `docker compose up` against just `compose.yaml`; mailpit isn't there to accidentally use.
Things to figure out
- Naming. `docker-compose.yaml` is the legacy name. Docker docs now prefer `compose.yaml`. Worth renaming as part of the same change.
- `make docker-up` target. Currently runs `docker compose up -d postgres mailpit`. With the split, it'd run the dev composition explicitly (`docker compose -f compose.yaml -f compose.dev.yaml up -d postgres mailpit`).
- `make migrate` and other Makefile targets that touch compose.
- CLAUDE.md and README docs that reference the current single file.
- Decide whether `compose.dev.yaml` is checked in (so first `docker compose up` Just Works) or `.example`-suffixed (forcing a copy step that signals "this is dev-only"). The first is friendlier; the second is more explicit. Lean toward checked-in given the file's already in git today.
Effort
Small. Real cost is the docs and Makefile updates, not the YAML split itself.
Out of scope (would be a separate issue)
- Compose profiles as an alternative to file split — Compose v2 supports `profiles:` per-service so `mailpit` could be tagged `profiles: [dev]` and skipped without `--profile dev`. That's lower-friction than two files but spreads the dev/prod distinction across every dev service, which is harder to reason about. Worth considering but the two-file split is the more conventional pattern.
Cross-linked from `docker-compose.yaml` via an inline `TODO` comment.
Follow-up from #108 review. Today `docker-compose.yaml` is used for both the local-dev experience and as the production-shape reference. The current file mixes:
If someone runs this compose file in production by accident, outbound mail silently funnels to the dev catch-all. The commit message and CLAUDE.md both say "drop the mailpit service in production," but the file itself doesn't enforce it.
Proposed split
Standard Docker Compose convention: base file + override file. Compose auto-merges `compose.yaml` + `compose.override.yaml` when both are present.
`compose.yaml` (production-shape, runs anywhere):
`compose.dev.yaml` (local dev override, gitignored copy or checked in as `compose.dev.example.yaml`):
Then:
Things to figure out
Effort
Small. Real cost is the docs and Makefile updates, not the YAML split itself.
Out of scope (would be a separate issue)
Cross-linked from `docker-compose.yaml` via an inline `TODO` comment.