✦ For everyone, free.

Practical knowledge for real and everyday life

Home

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 build directive 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_on without 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.

Content in this section