14.3.2 Compose Production Deployment
A focused guide to Compose Production Deployment, connecting core concepts with practical Docker and container operations.
Compose production deployment is the practice of using Docker Compose, typically thought of as a development convenience tool, as the actual deployment mechanism for a multi-service production stack on a single host or a small, fixed set of hosts, which is a legitimate and widely used pattern provided its specific production-readiness gaps are deliberately addressed rather than carried over unchanged from a development setup.
Why Compose works for production at a certain scale
Compose's core value, declaring a multi-service topology in one file and bringing it up or down as a unit, applies just as well to production as to development. For deployments that fit on a single host or a small number of hosts without needing automatic rescheduling across a dynamic cluster, Compose avoids the operational overhead of a full orchestrator while still providing declarative, version-controlled infrastructure definitions.
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d
Separating the base definition from production overrides
The override file pattern keeps the core service topology defined once while layering production-specific values, replica counts, resource limits, restart policies, on top:
services:
api:
build: .
ports:
- "3000:3000"
services:
api:
image: registry.example.com/my-api:1.4.0
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
environment:
- NODE_ENV=production
The production override removes the build directive entirely, since production should always run a pre-built, pushed image rather than building from source on the production host itself, which would otherwise leave production depending on build tooling and source code being present on a server that should ideally run as little beyond the container runtime as possible.
Restart policy is essential without an orchestrator's reconciliation loop
Compose itself does not continuously reconcile the desired state the way a full orchestrator does; once up -d completes, ongoing resilience depends entirely on each container's own restart policy:
services:
api:
restart: unless-stopped
db:
restart: unless-stopped
Without an explicit restart policy, a crashed container in a Compose-managed production stack simply stays down until someone notices and runs docker compose up -d again manually.
Health checks and dependency ordering
Compose supports health checks and can gate one service's startup on another service's health, which matters in production where starting an application before its database is actually ready to accept connections leads to a startup failure rather than a graceful wait:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
api:
image: my-api:latest
depends_on:
db:
condition: service_healthy
Without this condition, depends_on only guarantees startup order, not readiness, which is frequently the cause of an application container that starts before its database is actually accepting connections and then crashes on its first query attempt.
Logging configuration for production
Compose's default logging driver, if left unconfigured, can fill a production host's disk over time, since container output is retained indefinitely by default:
services:
api:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Applying this at every service definition, or globally through the Docker daemon's own configuration, is a small but important production hardening step that a development-oriented Compose file frequently omits, since disk exhaustion is rarely a concern during short-lived local development sessions.
Secrets in a Compose production stack
Compose supports a secrets mechanism analogous to Swarm's, mounting values as files rather than environment variables, which remains the preferred approach for production credentials even outside of Swarm mode:
services:
api:
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
The file-based secret source shown here should itself be protected with restrictive host filesystem permissions and excluded from version control, since Compose's file-based secrets do not provide the encryption-at-rest that Swarm's native secret store does.
Updating a Compose production stack safely
Deploying an update to a Compose-managed production stack should replace only the services that actually changed, rather than tearing down and recreating the entire stack on every deployment:
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d --no-deps api
The --no-deps flag avoids unnecessarily restarting dependent services that have not themselves changed, which matters for minimizing the blast radius and downtime of a routine, single-service update within a larger Compose stack.
Limitations to plan around
Compose alone provides no automatic rescheduling if a host fails entirely, no built-in load balancing across multiple hosts, and no rolling update orchestration as sophisticated as Swarm or a full orchestrator provides; a production Compose deployment that needs any of these capabilities has outgrown what Compose alone can offer and should plan a path toward Swarm mode or a different orchestration layer rather than attempting to recreate that functionality with custom scripting around Compose.
docker compose ps
Common mistakes
- Leaving a
builddirective active in a production Compose file, causing production deployments to depend on source code and build tooling being present on the server. - Omitting restart policies, leaving a crashed service down indefinitely until manually noticed and restarted.
- Using
depends_onwithout a health condition, allowing a service to start before a dependency it actually needs is ready to accept connections. - Leaving Compose's default, unbounded logging driver in place in production, risking disk exhaustion over time.
- Tearing down and recreating the entire stack on every deployment instead of updating only the changed service, increasing both downtime and risk for routine, single-service updates.
Compose production deployment is a sound choice for single-host or small, fixed-topology deployments, provided the override file explicitly addresses the gaps a development-oriented Compose file leaves open: removing build-from-source behavior, adding restart policies and health-aware dependency ordering, bounding log growth, and handling secrets and updates deliberately rather than carrying development defaults unchanged into production.