DEV Community

Peon Sh
Peon Sh

Posted on Originally published at peon.sh

Running Docker Compose in Production: A Practical Guide

Docker Compose is production-ready for single-host deployments. Learn restart policies, health checks, networks and update strategies.

Yes, Compose is production-ready
The advice that Compose is "only for development" dates from before restart policies, health checks, depends_on conditions and profiles existed. For single-host workloads, which describes the majority of real-world deployments, a compose file is a perfectly good production spec: declarative, versionable, and understood by every tool and every engineer.

What Compose does not do is multi-node orchestration and automated rollouts. The first you may never need; the second is what a deployment platform adds on top, driving Compose-shaped deployments with health-gated container swaps.

The non-negotiables
Five settings separate a dev compose file from a production one:

restart: unless-stopped on every long-running service, so a crash or reboot self-heals
healthcheck: on anything with dependents, and depends_on with condition: service_healthy so the app waits for the database to be ready, not merely started
Named volumes for all state; never bind-mount a database to a casual host path
Pinned image tags (postgres:17.2, not postgres:latest): latest turns every pull into a surprise upgrade
No published ports except the reverse proxy: services talk over the internal network; the proxy owns 80/443
A production skeleton
services:
app:
build: .
restart: unless-stopped
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
depends_on:
db:
condition: service_healthy
networks: [internal, proxy]
db:
image: postgres:17.2
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
retries: 5
networks: [internal]
volumes:
db-data:
networks:
internal:
proxy:
external: true
Secrets and configuration
The compose file in Git should contain ${PLACEHOLDERS}, never values. Inject real values at deploy time from your platform: Peon stores variables encrypted at rest and renders them when it deploys the stack, and workspace-level shared variables let ten services reference one API key without duplication. Run docker compose config locally to see the fully interpolated result when debugging.

Updates, rollbacks and disk hygiene
For stateless services, pulling new images and re-running up -d replaces containers with the new definition; with health checks in place, a platform-driven rollout only shifts traffic to containers that pass. Keep the previous image on disk and rollback is re-tagging and re-upping, seconds, not a rebuild.

Two operational habits keep a compose host healthy for years: schedule image and build-cache pruning (deploy-heavy hosts accumulate gigabytes weekly), and cap container log size in the Docker daemon config so a chatty service cannot fill the disk.

When you have outgrown it
Signals that single-host Compose is no longer enough: sustained load beyond what vertical scaling covers, a hard requirement to survive node failure in seconds, or several teams needing isolated slices of shared infrastructure. Until one of those is concretely true, the simplicity dividend of Compose on a well-sized VPS compounds every week.

Top comments (0)