BlogCI/CD

Docker Compose in production: the parts that bite

Restart policies, health-gated dependencies, resource caps, and secrets — what to fix before Compose meets real traffic.

Jul 2, 2024·4 min readIntermediate·By the SecOpsLog team · command-tested

Compose on a laptop forgives you: containers restart when you click, secrets live in .env, and depends_on means started, not ready. In production the same file meets reboots, traffic spikes, and anyone with docker inspect access. Production Compose is mostly discipline — health checks that gate dependencies, restart policies that actually restart, limits that keep one leak from taking the host, and secrets mounted as files instead of environment variables.

This note shapes a compose.yaml you would trust on a single VM or Swarm-style deploy: pinned images, service_healthy dependencies, and resource ceilings. Networking and volume patterns from Containers & Docker fundamentals still apply — Compose is orchestration-lite, not magic.

Boot sequence with health-gated dependencies

Without healthchecks, depends_on only waits for container start — your API will race the database and flap until retry logic saves you.

1db startspostgres with healthcheck2health probepg_isready passes3service_healthyCompose unblocks api4api startsmigrations succeed5reverse proxyroutes when api healthy6restart policyunless-stopped on crash7limits enforcedOOM kills one service

A production-shaped compose file

Pin images by digest or semver tag — not :latest. Declare healthchecks that reflect real readiness (database accepts connections, HTTP /healthz returns 200). Use the Compose spec's deploy.resources limits even on standalone Docker; they map to cgroup caps on the daemon.

compose.yaml
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets: [db_password]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
timeout: 5s
retries: 5
api:
image: ghcr.io/acme/api:1.4.2
restart: unless-stopped
depends_on:
db: { condition: service_healthy }
healthcheck:
test: ["CMD", "/app", "healthcheck"]
interval: 15s
retries: 3
deploy:
resources:
limits: { cpus: "1.0", memory: 512M }
secrets: [db_password, api_token]
secrets:
db_password:
file: ./secrets/db_password.txt
api_token:
file: ./secrets/api_token.txt

Secrets are not environment variables

Values in environment: show up in docker inspect, process listings, and crash dumps. Compose secrets mount files under /run/secrets/ with mode 0400 — your app reads the file at startup and never exports it to child shells. For multi-host production, external secret stores beat flat files on disk, but flat files still beat plaintext env in git.

bash — verify health before trusting trafficlive
docker compose up -d
docker compose ps
NAME STATUS HEALTH
db running healthy
api running healthy
api would not start until db healthcheck passed

Restart, logs, and host survival

Use restart: unless-stopped for daemons you want after reboot — not always, which fights intentional stops during maintenance. Configure log rotation (logging.options max-size) so one chatty container does not fill the disk. Resource limits are your poor man's Kubernetes requests/limits: without them, a memory leak in the API container becomes an OOM kill for the entire host.

Put an edge proxy in the same compose file with explicit network aliases and read-only root filesystem where the app allows it. Document a runbook for docker compose pull && docker compose up -d upgrades — pinned tags mean you choose when to absorb upstream base image patches instead of silently floating on :latest every restart.

Demo vs production Compose
Laptop defaults
depends_on: [db]
secrets in .env
no healthchecks
no CPU/memory caps
:latest tags
Production habits
condition: service_healthy
file-based secrets
app-native health probes
deploy.resources.limits
pinned image digests
Compose is not Kubernetes
Single-host Compose lacks pod disruption budgets, network policies, and horizontal scale. It is fine for edge appliances and small stacks — know when you have outgrown it. Secrets on disk still need filesystem permissions and encryption at rest on the VM.

Where this goes next

When replicas, rolling updates, and cluster networking enter the picture, graduate to Kubernetes or Swarm with the same health and secret discipline. Deep dives on Compose networking, volumes, and production ops live in Docker in depth — same containers, harder uptime requirements.

Go deeper in a courseDocker in depthCompose, networking, storage, and running containers reliably in production.View course

Related posts