Docker Compose

Learn how to use Docker Compose to manage multi-container applications with ease.

What is Docker Compose?

Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services, networks, and volumes, then start everything with a single command.

Why Use Docker Compose?

Docker Compose is perfect for:

  • Development environments with multiple services
  • Applications that need databases, caches, and other dependencies
  • Testing complex application stacks
  • Simplifying container orchestration for small to medium applications

Basic Docker Compose File

Here’s a simple docker-compose.yml for a web application with a database:

version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - api

  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DB_HOST=database
    depends_on:
      - database

  database:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Key Docker Compose Concepts

Services

Services define the containers that make up your application. Each service runs one container image.

services:
  web:
    image: nginx:alpine
  redis:
    image: redis:alpine

Images

You can use existing images or build from a Dockerfile:

services:
  # Use existing image
  web:
    image: nginx:alpine

  # Build from Dockerfile
  api:
    build: ./api
    # or with build context and Dockerfile path
    build:
      context: ./api
      dockerfile: Dockerfile.prod

Ports

Map ports between the host and containers:

services:
  web:
    image: nginx
    ports:
      - "8080:80"    # host:container
      - "80:80"     # same port
      - "443"       # random host port to container port 443

Environment Variables

Set environment variables for your services:

services:
  api:
    image: node:18
    environment:
      - NODE_ENV=production
      - API_KEY=secret123
      - DB_HOST=database
    # or using a map
    environment:
      NODE_ENV: production
      API_KEY: secret123

Volumes

Persist data and share files between host and containers:

services:
  database:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data  # named volume
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql  # bind mount

volumes:
  postgres_data:  # named volume definition

Networks

Create custom networks for service communication:

services:
  web:
    image: nginx
    networks:
      - frontend

  api:
    image: node
    networks:
      - frontend
      - backend

networks:
  frontend:
  backend:

Dependencies

Control service startup order:

services:
  web:
    image: nginx
    depends_on:
      - api

  api:
    image: node
    depends_on:
      - database

  database:
    image: postgres

Common Docker Compose Commands

Start Services

# Start all services in detached mode
docker-compose up -d

# Start specific service
docker-compose up web

# Start with rebuild
docker-compose up --build

Stop Services

# Stop and remove containers
docker-compose down

# Stop but keep containers
docker-compose stop

# Stop and remove everything (volumes, networks)
docker-compose down --volumes

View Status

# List running services
docker-compose ps

# View service logs
docker-compose logs

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

# Show logs from last 10 minutes
docker-compose logs --since=10m

Execute Commands

# Run command in service
docker-compose exec web ls /usr/share/nginx/html

# Get shell in service
docker-compose exec api sh

# Run one-off command
docker-compose run --rm web nginx -t

Real-World Examples

Full-Stack Application

version: '3.8'

services:
  # Frontend (React)
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - REACT_APP_API_URL=http://localhost:5000
    volumes:
      - ./frontend:/app
      - /app/node_modules
    depends_on:
      - backend

  # Backend (Node.js)
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://user:password@database:5432/myapp
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./backend:/app
      - /app/node_modules
    depends_on:
      - database
      - redis

  # Database (PostgreSQL)
  database:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql

  # Cache (Redis)
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  # Reverse Proxy (Nginx)
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
    depends_on:
      - frontend
      - backend

volumes:
  postgres_data:
  redis_data:

Development vs Production

Use multiple Compose files for different environments:

docker-compose.yml (base):

version: '3.8'

services:
  web:
    build: .
    environment:
      - NODE_ENV=development
    volumes:
      - .:/app
      - /app/node_modules

docker-compose.prod.yml (production overrides):

version: '3.8'

services:
  web:
    environment:
      - NODE_ENV=production
    volumes:  # Remove development volumes

Run with production overrides:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

Environment Variables

Use .env file for configuration:

# .env
NODE_ENV=development
DB_HOST=localhost
DB_PORT=5432

Reference in docker-compose.yml:

services:
  api:
    image: node:18
    environment:
      - NODE_ENV=${NODE_ENV:-production}
      - DB_HOST=${DB_HOST}
      - DB_PORT=${DB_PORT}

Health Checks

Add health checks to your services:

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

Resources

Last updated on