Skip to content

Split docker-compose.yaml into compose.yaml + compose.dev.yaml (mailpit-only override) #127

@jiashuoz

Description

@jiashuoz

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

  1. Naming. `docker-compose.yaml` is the legacy name. Docker docs now prefer `compose.yaml`. Worth renaming as part of the same change.
  2. `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`).
  3. `make migrate` and other Makefile targets that touch compose.
  4. CLAUDE.md and README docs that reference the current single file.
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions