Skip to main content
ContainersIntermediate

Docker Compose for Production: Best Practices and Patterns

Learn production-ready Docker Compose patterns including resource limits, health checks, logging, secrets management, and network isolation.

N
Neeraj Jha
·Updated September 11, 2026·4 min read
Docker Compose for Production: Best Practices and Patterns

Docker Compose is often dismissed as a "development-only" tool, but with the right patterns it can power small-to-medium production workloads reliably. This guide covers the practices that separate a throwaway dev stack from a production-grade Compose deployment.

Resource Limits

Every service should declare CPU and memory limits. Without them a single runaway process can starve the entire host.

yaml
services:
  api:
    image: myorg/api:1.4.2
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M

Reservations guarantee a baseline; limits cap the maximum. Use docker stats to baseline real usage before setting values.

Health Checks

Compose can restart unhealthy containers automatically when you define health checks:

yaml
services:
  api:
    image: myorg/api:1.4.2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    restart: unless-stopped

The start_period gives the container time to initialize before health checks count against it.

Restart Policies

PolicyBehavior
noNever restart (default)
alwaysRestart on any exit
unless-stoppedRestart unless explicitly stopped
on-failureRestart only on non-zero exit code

For production, unless-stopped is usually the best choice. It survives host reboots (when the Docker daemon starts) but respects intentional docker compose stop commands.

Logging Configuration

Default JSON-file logs grow unbounded. Always set limits:

yaml
services:
  api:
    image: myorg/api:1.4.2
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "5"

For centralized logging, use the fluentd or gelf driver to ship logs to an aggregator.

Environment Variable Management

Never hard-code secrets in docker-compose.yml. Use a .env file that is excluded from version control:

bash
# .env (git-ignored)
POSTGRES_PASSWORD=supersecretvalue
API_SECRET_KEY=anothersecret
yaml
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

For extra security, use Docker secrets with docker secret create or mount secret files as read-only volumes.

Named Volumes and Backups

Always use named volumes for persistent data:

yaml
volumes:
  pgdata:
    driver: local

services:
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data

Back up named volumes with:

bash
docker run --rm -v pgdata:/data -v $(pwd):/backup alpine tar czf /backup/pgdata-backup.tar.gz -C /data .

Network Isolation

Create explicit networks so services only communicate with what they need:

yaml
networks:
  frontend:
  backend:

services:
  nginx:
    networks: [frontend, backend]
  api:
    networks: [backend]
  db:
    networks: [backend]

The database is not reachable from the frontend network, reducing the attack surface.

Pinning Image Versions

Never use :latest in production. Pin to a specific tag or digest:

yaml
services:
  api:
    image: myorg/api:1.4.2@sha256:abc123...

This guarantees reproducibility across deployments and makes rollbacks straightforward.

Deployment Checklist

  • All images pinned to specific versions
  • Resource limits set on every service
  • Health checks defined for application containers
  • Restart policy set to unless-stopped
  • Log rotation configured
  • Secrets managed via .env or Docker secrets
  • Named volumes for all persistent data
  • Explicit networks for service isolation
  • Backups automated and tested

Docker Compose is not Kubernetes, but for single-host deployments it is simple, predictable, and battle-tested when configured properly.

Tagged with

Enjoyed this article?

Get more DevOps insights delivered to your inbox.

Get new posts by email

Subscribe to get an email when a new blog post is published. Skip anytime.

No spam, unsubscribe anytime.

N

Written by

Neeraj Jha

Platform administrator and lead writer.

View all posts

Discussion

0 comments

Sign in to join the conversation.

Be the first to comment

Start a conversation about this post

Share: