Contents

Chapter 1

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.

yaml
services:
  app:
    networks:
      - backend

  db:
    networks:
      - backend

networks:
  backend:
    driver: bridge

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:

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

# In the app stack
networks:
  shared-db:
    external: true

Exposing Ports Safely

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

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

  # Internal only — no port mapping needed
  api:
    expose:
      - "3000"

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

yaml
ports:
  - "127.0.0.1:5432:5432"

Volume Management

Named Volumes vs Bind Mounts

TypeUse CaseExample
Named volumeDatabase data, persistent statedb-data:/var/lib/postgresql/data
Bind mountConfig files, source code (dev only)./nginx.conf:/etc/nginx/nginx.conf:ro
tmpfsTemporary files, cachestmpfs: /tmp
yaml
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

Read-Only Mounts

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

yaml
volumes:
  - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro

Volume Backup Pattern

bash
# 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 .

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

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

TCP Health Check

For services that don't have an HTTP endpoint:

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

Important Parameters

ParameterPurposeRecommended
intervalTime between checks10–30s
timeoutMax time to wait for a check to respond5–10s
retriesFailures before marking unhealthy3–5
start_periodGrace period for slow-starting services30–120s

Environment Variables & Secrets

Using .env Files

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

yaml
services:
  app:
    environment:
      - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}

Docker Secrets (Swarm Mode)

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

yaml
services:
  app:
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

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)

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.

yaml
services:
  elasticsearch:
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2g
        reservations:
          cpus: "0.5"
          memory: 512m

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:

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

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:

yaml
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

Init Container Pattern

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

yaml
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

Worker Pattern

Scale background workers independently from the web process:

yaml
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

Reverse Proxy with Traefik

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

Basic Label Configuration

yaml
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"

Path-Based Routing

yaml
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"

Rate Limiting Middleware

yaml
labels:
  - "traefik.http.middlewares.rate-limit.ratelimit.average=100"
  - "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
  - "traefik.http.routers.app.middlewares=rate-limit"

Logging

JSON Logging Driver

Configure structured logging for easier parsing by ELK or Loki:

yaml
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

Sending Logs to Logstash

yaml
logging:
  driver: gelf
  options:
    gelf-address: "udp://localhost:12201"
    tag: "myapp"

Startup Order & Dependencies

Basic Ordering

yaml
depends_on:
  - db
  - redis

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

Wait for Healthy Dependencies

yaml
depends_on:
  db:
    condition: service_healthy
  redis:
    condition: service_healthy

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

Wait for One-Shot Tasks

yaml
depends_on:
  migrate:
    condition: service_completed_successfully

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)*

Chapter 2

Docker Compose Templates

Production-ready Docker Compose stacks for web apps, monitoring, logging, databases, and CI runners.

Drop-in Compose files for the most common self-hosted infrastructure. Each stack is tested, commented, and ready to deploy with a single docker compose up -d. Includes a Traefik reverse proxy with automatic TLS and a stack manager script.


Chapter 3
🔒 Available in full product

What You Get

You’ve reached the end of the free preview

Get the full Docker Compose Templates and unlock everything.

All Chapters

Get the complete guide with every chapter unlocked, including code samples, diagrams, and best practices.

Full Tool Suite

Access all interactive tools with complete data, all workload profiles, and the full scenario library.

Source Files

Downloadable source code, configuration files, and working examples from every chapter.

Lifetime Updates

Free updates for life. Every new chapter, tool, and improvement included.

Buy Now — $29 →
📦 Free sample included — download another copy for the full product.
Docker Compose Templates v1.0.0 — Free Preview