DEV Community

Thesius Code
Thesius Code

Posted on • Originally published at datanest-stores.pages.dev

Docker Compose Templates: Docker Compose Patterns & Best Practices

Docker Compose Patterns & Best Practices

A reference guide for production Docker Compose deployments. Covers networking, volumes, health checks, secrets management, resource limits, and multi-service design patterns.


Table of Contents

  1. Networking Patterns
  2. Volume Management
  3. Health Checks
  4. Environment Variables & Secrets
  5. Resource Limits
  6. Multi-Service Patterns
  7. Reverse Proxy with Traefik
  8. Logging
  9. Startup Order & Dependencies
  10. Production Checklist

Networking Patterns

Isolated Stack Networks

Each Compose stack should define its own bridge network. Services within the same network can reach each other by container name. Services in different stacks are isolated by default.

services:
  app:
    networks:
      - backend

  db:
    networks:
      - backend

networks:
  backend:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Shared Networks for Cross-Stack Communication

When two stacks need to talk (e.g., an app stack reaching a shared database), use an external network:

# In the database stack
networks:
  shared-db:
    name: shared-db
    driver: bridge

# In the app stack
networks:
  shared-db:
    external: true
Enter fullscreen mode Exit fullscreen mode

Exposing Ports Safely

Only expose ports that external clients need. Internal service-to-service traffic should use the Docker network, not published ports.

services:
  # Public-facing — expose port
  nginx:
    ports:
      - "443:443"

  # Internal only — no port mapping needed
  api:
    expose:
      - "3000"
Enter fullscreen mode Exit fullscreen mode

Bind to localhost during development to avoid exposing services to your LAN:

ports:
  - "127.0.0.1:5432:5432"
Enter fullscreen mode Exit fullscreen mode

Volume Management

Named Volumes vs Bind Mounts

Type Use Case Example
Named volume Database data, persistent state db-data:/var/lib/postgresql/data
Bind mount Config files, source code (dev only) ./nginx.conf:/etc/nginx/nginx.conf:ro
tmpfs Temporary files, caches tmpfs: /tmp
services:
  postgres:
    volumes:
      # Named volume — Docker manages the directory
      - pgdata:/var/lib/postgresql/data
      # Bind mount — read-only config file
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

volumes:
  pgdata:
    driver: local
Enter fullscreen mode Exit fullscreen mode

Read-Only Mounts

Mount config files as read-only (:ro) to prevent accidental writes:

volumes:
  - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
Enter fullscreen mode Exit fullscreen mode

Volume Backup Pattern

# Backup a named volume to a tar archive
docker run --rm \
  -v pgdata:/data:ro \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
Enter fullscreen mode Exit fullscreen mode

Health Checks

Every long-running service should define a health check. This enables depends_on with condition: service_healthy and lets Docker report accurate status.

HTTP Health Check

healthcheck:
  test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 30s
Enter fullscreen mode Exit fullscreen mode

TCP Health Check

For services that don't have an HTTP endpoint:

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres || exit 1"]
  interval: 10s
  timeout: 5s
  retries: 5
Enter fullscreen mode Exit fullscreen mode

Important Parameters

Parameter Purpose Recommended
interval Time between checks 10–30s
timeout Max time to wait for a check to respond 5–10s
retries Failures before marking unhealthy 3–5
start_period Grace period for slow-starting services 30–120s

Environment Variables & Secrets

Using .env Files

Docker Compose automatically loads a .env file from the project directory:

services:
  app:
    environment:
      - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
Enter fullscreen mode Exit fullscreen mode

Docker Secrets (Swarm Mode)

For production deployments on Docker Swarm, use secrets instead of environment variables:

services:
  app:
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
Enter fullscreen mode Exit fullscreen mode

Separation of Concerns

Keep different types of configuration separate:

.env              # Shared defaults (ports, versions)
.env.local        # Machine-specific overrides (not committed)
.env.production   # Production values (committed, no real secrets)
Enter fullscreen mode Exit fullscreen mode

Never commit real passwords or tokens. Use a secrets manager (Vault, AWS Secrets Manager) for production.


Resource Limits

Always set memory and CPU limits in production to prevent a single container from consuming all host resources.

services:
  elasticsearch:
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2g
        reservations:
          cpus: "0.5"
          memory: 512m
Enter fullscreen mode Exit fullscreen mode

Note: deploy.resources works in both Swarm mode and standalone Compose (with Docker Compose v2+).

JVM-Based Services

For Elasticsearch, Logstash, and other JVM services, set both the container memory limit and the JVM heap:

environment:
  - ES_JAVA_OPTS=-Xms1g -Xmx1g   # JVM heap
deploy:
  resources:
    limits:
      memory: 2g                   # Container limit (heap + overhead)
Enter fullscreen mode Exit fullscreen mode

Rule of thumb: container limit = JVM heap × 2 (to allow for off-heap, GC, and OS overhead).


Multi-Service Patterns

Sidecar Pattern

Run helper containers alongside your main service:

services:
  app:
    image: myapp:latest

  # Log shipper sidecar
  filebeat:
    image: docker.elastic.co/beats/filebeat:8.13.4
    volumes:
      - app-logs:/var/log/app:ro
    depends_on:
      - app
Enter fullscreen mode Exit fullscreen mode

Init Container Pattern

Run a one-shot container to prepare state before the main service starts:

services:
  migrate:
    image: myapp:latest
    command: ["python", "manage.py", "migrate"]
    depends_on:
      db:
        condition: service_healthy

  app:
    image: myapp:latest
    depends_on:
      migrate:
        condition: service_completed_successfully
Enter fullscreen mode Exit fullscreen mode

Worker Pattern

Scale background workers independently from the web process:

services:
  web:
    image: myapp:latest
    command: ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]

  worker:
    image: myapp:latest
    command: ["celery", "-A", "tasks", "worker", "--loglevel=info"]
    deploy:
      replicas: 3
Enter fullscreen mode Exit fullscreen mode

Reverse Proxy with Traefik

Traefik auto-discovers services via Docker labels. No manual Nginx config files needed.

Basic Label Configuration

services:
  app:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.services.app.loadbalancer.server.port=3000"
Enter fullscreen mode Exit fullscreen mode

Path-Based Routing

labels:
  - "traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)"
  - "traefik.http.middlewares.api-strip.stripprefix.prefixes=/api"
  - "traefik.http.routers.api.middlewares=api-strip"
Enter fullscreen mode Exit fullscreen mode

Rate Limiting Middleware

labels:
  - "traefik.http.middlewares.rate-limit.ratelimit.average=100"
  - "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
  - "traefik.http.routers.app.middlewares=rate-limit"
Enter fullscreen mode Exit fullscreen mode

Logging

JSON Logging Driver

Configure structured logging for easier parsing by ELK or Loki:

services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"     # Rotate after 10 MB
        max-file: "3"       # Keep 3 rotated files
        tag: "{{.Name}}"    # Tag with container name
Enter fullscreen mode Exit fullscreen mode

Sending Logs to Logstash

logging:
  driver: gelf
  options:
    gelf-address: "udp://localhost:12201"
    tag: "myapp"
Enter fullscreen mode Exit fullscreen mode

Startup Order & Dependencies

Basic Ordering

depends_on:
  - db
  - redis
Enter fullscreen mode Exit fullscreen mode

This only waits for the container to start, not for the service to be ready.

Wait for Healthy Dependencies

depends_on:
  db:
    condition: service_healthy
  redis:
    condition: service_healthy
Enter fullscreen mode Exit fullscreen mode

This is the correct approach — it waits until the health check passes before starting the dependent service.

Wait for One-Shot Tasks

depends_on:
  migrate:
    condition: service_completed_successfully
Enter fullscreen mode Exit fullscreen mode

Useful for database migrations or seed scripts that must finish before the app starts.


Production Checklist

Before deploying to production, verify each item:

  • [ ] All services have health checks with appropriate start_period values
  • [ ] Resource limits (memory + CPU) are set for every service
  • [ ] Named volumes are used for persistent data (not bind mounts)
  • [ ] No hardcoded secrets — all passwords come from .env or Docker secrets
  • [ ] Restart policy is unless-stopped or always for every service
  • [ ] Log rotation is configured (json-file driver with max-size and max-file)
  • [ ] Networks are isolated — only expose what needs to be public
  • [ ] Images use specific tags (not :latest) for reproducibility
  • [ ] Ports are not exposed for internal-only services
  • [ ] Backups are configured for database volumes
  • [ ] TLS is enabled via Traefik or an external load balancer
  • [ ] Monitoring (Prometheus + Grafana or similar) is deployed

Part of Docker Compose Templates — (c) 2026 Datanest Digital (datanest.dev)


This is 1 of 6 resources in the DevOps Toolkit Pro toolkit. Get the complete [Docker Compose Templates] with all files, templates, and documentation for $XX.

Get the Full Kit →

Or grab the entire DevOps Toolkit Pro bundle (6 products) for $178 — save 30%.

Get the Complete Bundle →


Related Articles

Top comments (0)