Skip to content
beginner Phase 7 · Docker

Docker Compose

Define and run multi-container applications with docker-compose.yml for development and testing environments.

55m
0 problems
Topic Progress 0%

Compose Basics

Compose Basics

Complete docker-compose.yml

version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - ./src:/app/src          # Bind mount (dev only)
      - node_modules:/app/node_modules
    networks:
      - app-network
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

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

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - web
    networks:
      - app-network
    restart: unless-stopped

volumes:
  postgres-data:
  redis-data:
  node_modules:

networks:
  app-network:
    driver: bridge

Basic Commands

# Start all services
docker compose up

# Start in background
docker compose up -d

# Stop all services
docker compose down

# Stop and remove volumes
docker compose down -v

# View logs
docker compose logs
 docker compose logs -f web    # Follow specific service

# List services
docker compose ps

# Execute command in service
docker compose exec web bash
docker compose exec db psql -U postgres

# Build/rebuild images
docker compose build
docker compose build --no-cache web

# Scale a service
docker compose up -d --scale web=3

# View resource usage
docker compose top

# Restart a service
docker compose restart web

# View configuration
docker compose config

Environment Variables and Secrets

Environment Variables and Secrets

Environment Variable Sources

services:
  web:
    # Inline environment variables
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=info

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

    # With defaults and variable substitution
    environment:
      - NODE_ENV=${NODE_ENV:-development}
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://redis:${REDIS_PORT:-6379}
# .env file
NODE_ENV=production
DB_USER=postgres
DB_PASSWORD=s3cr3t_p4ssw0rd
DB_NAME=myapp
REDIS_PORT=6379

# docker-compose.yml references these automatically
# No need to reference .env in env_file if it's in the same directory

Docker Secrets (Swarm mode)

# For Docker Swarm (not Compose standalone)
services:
  web:
    secrets:
      - db_password
      - api_key

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    external: true    # Pre-created secret

Development vs Production

# docker-compose.override.yml (dev - loaded automatically)
services:
  web:
    build:
      target: development
    volumes:
      - ./src:/app/src    # Hot reload
    environment:
      - NODE_ENV=development
      - DEBUG=true
    command: npm run dev

# docker-compose.prod.yml (production)
services:
  web:
    build:
      target: production
    environment:
      - NODE_ENV=production
    command: node dist/server.js
    deploy:
      replicas: 3

# Usage:
# Development: docker compose up (uses override automatically)
# Production: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Service Dependencies and Profiles

Service Dependencies and Profiles

Health Check Dependencies

services:
  web:
    depends_on:
      db:
        condition: service_healthy    # Wait for healthy
      redis:
        condition: service_started    # Just wait for start

  db:
    image: postgres:15
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10

  redis:
    image: redis:7
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10

Profiles

services:
  web:
    # Always starts (no profile)
    image: myapp

  db:
    # Always starts
    image: postgres:15

  # Dev tools (only with --profile dev)
  mailhog:
    image: mailhog/mailhog
    profiles: [dev]

  adminer:
    image: adminer
    profiles: [dev]

  # Monitoring (only with --profile monitoring)
  prometheus:
    image: prom/prometheus
    profiles: [monitoring]

  grafana:
    image: grafana/grafana
    profiles: [monitoring]

# Usage:
# docker compose up -d                         # Only web + db
# docker compose --profile dev up -d           # web + db + mailhog + adminer
# docker compose --profile dev --profile monitoring up -d  # All services

Init Containers Pattern

services:
  # Init job that runs before the main service
  db-init:
    image: postgres:15
    command: bash -c "
      until pg_isready; do sleep 1; done &&
      psql -U postgres -c 'CREATE DATABASE IF NOT EXISTS myapp;'
    "
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      - app-network

  web:
    image: myapp
    depends_on:
      db-init:
        condition: service_completed_successfully

Complete Multi-Service Stack

# Full-stack development environment
services:
  frontend:
    build: ./frontend
    ports:
      - "5173:5173"
    volumes:
      - ./frontend/src:/app/src
    networks:
      - app-network

  backend:
    build: ./backend
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-network

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - app-network

  mail:
    image: mailhog/mailhog
    ports:
      - "1025:1025"    # SMTP
      - "8025:8025"    # Web UI
    profiles: [dev]
    networks:
      - app-network

  adminer:
    image: adminer
    ports:
      - "8081:8080"
    profiles: [dev]
    networks:
      - app-network

volumes:
  postgres-data:

networks:
  app-network:
    driver: bridge