Docs
/
Docker Kubernetes
Chapter 6

06 — Docker Compose

What is Docker Compose?

Define and run multi-container applications with a single YAML file.

# Instead of running 5 separate docker run commands:
docker compose up -d      # Start everything
docker compose down       # Stop everything

Basic Example

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://postgres:secret@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:

Commands

docker compose up -d             # Start all services (detached)
docker compose up -d --build     # Rebuild images and start
docker compose down              # Stop and remove containers
docker compose down -v           # + remove volumes
docker compose ps                # List running services
docker compose logs              # View all logs
docker compose logs -f api       # Follow specific service logs
docker compose exec api sh       # Shell into service
docker compose restart api       # Restart one service
docker compose pull              # Pull latest images
docker compose build             # Rebuild images
docker compose stop              # Stop without removing
docker compose start             # Start stopped services

Build Configuration

services:
  api:
    build:
      context: .                    # Build context path
      dockerfile: Dockerfile.prod   # Custom Dockerfile
      target: production            # Multi-stage target
      args:
        NODE_ENV: production        # Build arguments
    image: my-app:latest            # Tag the built image

Networking

services:
  api:
    networks:
      - frontend
      - backend

  web:
    networks:
      - frontend        # Can reach api, NOT db

  db:
    networks:
      - backend         # Can reach api, NOT web

networks:
  frontend:
  backend:

By default, all services share one network and can reach each other by service name.


Environment Variables

services:
  api:
    # Inline
    environment:
      - NODE_ENV=production
      - PORT=3000

    # From .env file
    env_file:
      - .env
      - .env.local

    # From host environment
    environment:
      - API_KEY    # Passes host's $API_KEY into container
# .env file (auto-loaded by Compose)
POSTGRES_PASSWORD=secret
API_KEY=abc123

Profiles

Run subsets of services for different scenarios.

services:
  api:
    build: .
    ports: ["3000:3000"]

  db:
    image: postgres:16-alpine

  redis:
    image: redis:7-alpine

  # Only runs with --profile debug
  adminer:
    image: adminer
    ports: ["8080:8080"]
    profiles: [debug]

  # Only runs with --profile monitoring
  prometheus:
    image: prom/prometheus
    profiles: [monitoring]
docker compose up -d                          # api + db + redis
docker compose --profile debug up -d          # + adminer
docker compose --profile monitoring up -d     # + prometheus

Override Files

# docker-compose.yml (base)
services:
  api:
    image: my-app:latest
    ports: ["3000:3000"]

# docker-compose.override.yml (dev — auto-merged)
services:
  api:
    build: .
    volumes:
      - ./src:/app/src
    environment:
      - DEBUG=true

# docker-compose.prod.yml (production)
services:
  api:
    restart: always
    deploy:
      replicas: 3
# Dev (auto-merges override)
docker compose up -d

# Production (explicit file)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Full-Stack Example

services:
  frontend:
    build: ./frontend
    ports: ["3000:3000"]
    depends_on: [api]

  api:
    build: ./backend
    ports: ["4000:4000"]
    environment:
      DATABASE_URL: postgres://postgres:secret@db:5432/app
      REDIS_URL: redis://cache:6379
      JWT_SECRET: my-secret
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready -U postgres
      interval: 5s
      retries: 5

  cache:
    image: redis:7-alpine

  worker:
    build: ./backend
    command: node dist/worker.js
    environment:
      REDIS_URL: redis://cache:6379
    depends_on: [cache]

volumes:
  pgdata:

Key Takeaways

  • docker-compose.yml defines your entire stack in one file
  • Services communicate by name (e.g., db, cache, api)
  • Use depends_on with condition: service_healthy for startup ordering
  • Use volumes for persistent data, bind mounts for dev hot-reload
  • Use profiles to include optional services (debug tools, monitoring)
  • Use override files for environment-specific config (dev vs prod)
  • docker compose up -d --build is the most common dev command