Skip to content
← Blog

Docker Compose File Reference

8 min readDevOps

Docker Compose defines multi-container applications in a single YAML file. Instead of running multiple docker run commands with complex flags, you declare your entire stack — web servers, databases, caches, workers — in compose.yaml and bring it up with docker compose up.

This reference covers the most commonly used Compose directives with practical examples.

Basic Structure

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
  
  api:
    build: ./api
    ports:
      - "3000:3000"
    depends_on:
      - db
  
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

A Compose file has three top-level keys: services (required), volumes (optional), and networks (optional).

Services

Image vs Build

services:
  # Use a pre-built image
  redis:
    image: redis:7-alpine

  # Build from a Dockerfile
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
      args:
        NODE_ENV: production

Ports

ports:
  - "8080:80"          # host:container
  - "3000:3000"        # same port
  - "127.0.0.1:9090:9090"  # bind to localhost only

Use expose instead of ports for container-to-container communication without host binding:

expose:
  - "6379"

Environment Variables

environment:
  DATABASE_URL: postgres://user:pass@db:5432/myapp
  NODE_ENV: production

# Or from a file
env_file:
  - .env

Volumes

volumes:
  - ./src:/app/src          # bind mount (host path)
  - node_modules:/app/node_modules  # named volume
  - /app/dist               # anonymous volume

Named volumes persist data across container restarts and recreations:

volumes:
  pgdata:
    driver: local

Health Checks

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

Dependencies

depends_on:
  db:
    condition: service_healthy
  redis:
    condition: service_started

Using condition: service_healthy waits for the dependency's health check to pass before starting.

Restart Policy

restart: unless-stopped  # restart on failure and on daemon restart, but not if manually stopped
# Other options: "no", "always", "on-failure"

Resource Limits

deploy:
  resources:
    limits:
      cpus: "0.5"
      memory: 512M
    reservations:
      cpus: "0.25"
      memory: 256M

Networks

services:
  web:
    networks:
      - frontend
  api:
    networks:
      - frontend
      - backend
  db:
    networks:
      - backend

networks:
  frontend:
  backend:

Services on the same network can reach each other by service name. Separate networks isolate traffic.

Common Patterns

Web App + Database + Cache

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/app
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine

volumes:
  pgdata:

Development vs Production

Use compose.override.yaml for development-specific settings (auto-loaded by Docker Compose):

# compose.yaml (base)
services:
  api:
    image: myapp:latest

# compose.override.yaml (development)
services:
  api:
    build: .
    volumes:
      - ./src:/app/src
    environment:
      DEBUG: "true"

For production, use a separate file: docker compose -f compose.yaml -f compose.prod.yaml up

Common Mistakes

  1. Using latest tag in productionimage: postgres:latest can change at any time. Pin specific versions: postgres:16.2-alpine.
  2. Storing secrets in compose.yaml — environment variables in the file are visible in version control. Use Docker secrets or external secret management.
  3. Not using health checks with depends_ondepends_on without condition: service_healthy only waits for the container to start, not for the service to be ready.
  4. Bind mounts overwriting node_modules — mounting ./:/app overwrites the container's node_modules. Use a named volume for node_modules.
  5. Not declaring volumes — data in containers is ephemeral. If the container is recreated, any data not in a volume is lost.

Generate Templates

StackCache Docker & Kubernetes Templates generates validated Dockerfile, Compose, and Kubernetes YAML starting points for common stacks — all locally in your browser.

Try it yourself

Open the tool mentioned in this guide — it runs locally in your browser, no account needed.

Open tool