Skip to content

Deploy with Docker Compose

The production Compose stack runs the gateway, PostgreSQL, and Redis. Put a TLS reverse proxy in front of it; the published application and metrics ports bind to loopback by default.

Copy .env.prod.example to .env.prod, set its mode to 0600, and replace every required value. Use an immutable image tag or digest—there is deliberately no latest default.

At minimum, set:

  • EMCP_TAG and, if needed, EMCP_IMAGE
  • EMCP_BASE_URL to the public HTTPS origin
  • EMCP_PG_PASSWORD
  • EMCP_KEYS_DIR
  • SMTP notification settings

Generate the four long-lived key files before the first start:

Terminal window
docker compose -f compose.prod.yml --env-file .env.prod \
--profile init up keygen

Copy the printed key_… identifier into EMCP_KEK_ACTIVE_ID, then start the stack:

Terminal window
docker compose -f compose.prod.yml --env-file .env.prod up -d

The migration service applies pending migrations before the gateway becomes ready. Verify the deployment through /healthz and /readyz, not merely by checking that the container is running.

Forward HTTPS traffic to the application listener, normally 127.0.0.1:8080. Preserve WebSocket upgrade headers and set the upstream read timeout to at least 14,700 seconds for long waits. Do not publish the metrics listener (127.0.0.1:9090) to the internet.

The default gateway process also runs background workers. For a two-tier deployment, set:

EMCP_WORKERS_IN_GATEWAY=false
EMCP_EXPECT_HA=true

Then include the tiered profile:

Terminal window
docker compose -f compose.prod.yml --env-file .env.prod \
--profile tiered up -d

Keep at least one worker running. Without one, approvals, outgoing webhooks, cleanup sweeps, and security posture rechecks stop progressing.