Docker Compose Guide

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.

💡 Tip: Docker Compose simplifies managing complex applications with multiple containers that need to work together.

Why Use Docker Compose?

  • 📝 Define all services in a single file
  • 🔄 Start/stop all services with one command
  • 🌐 Automatic networking between containers
  • 💾 Manage volumes and persistent data
  • 🔧 Easy environment variable configuration

Installation

Standalone Compose V2

# Linux
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Verify installation
docker-compose --version

Docker Desktop

Docker Compose is included in Docker Desktop for Windows and macOS.

Basic docker-compose.yml

version: '3.8'

services:
  web:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    environment:
      - NGINX_HOST=localhost
    networks:
      - mynetwork

networks:
  mynetwork:
    driver: bridge

Common Service Configuration

Image

services:
  web:
    image: nginx:latest  # Use existing image
    # OR
    build: .             # Build from Dockerfile
    # OR
    build:
      context: ./app
      dockerfile: Dockerfile.dev

Ports

services:
  web:
    ports:
      - "8080:80"           # host:container
      - "443:443"
      - "3000:3000"
    expose:
      - "8080"              # Expose internally only

Volumes

services:
  web:
    volumes:
      # Bind mount
      - ./data:/var/lib/data
      # Named volume
      - db-data:/var/lib/db
      # Anonymous volume
      - /cache

volumes:
  db-data:
    driver: local

Environment Variables

services:
  web:
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://localhost/db
      - DEBUG=false
    # OR from file
    env_file:
      - .env
      - .env.production

Networks

services:
  web:
    networks:
      - frontend
      - backend

  db:
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # No external access

Dependencies

services:
  web:
    depends_on:
      - db
      - redis

  db:
    image: postgres:15
    # web will wait for db to start

Complete Example

Full-stack application with frontend, backend, and database:

version: '3.8'

services:
  # Frontend
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - REACT_APP_API_URL=http://backend:5000
    depends_on:
      - backend
    networks:
      - app-network

  # Backend API
  backend:
    build: ./backend
    ports:
      - "5000:5000"
    volumes:
      - ./backend:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    networks:
      - app-network

  # Database
  db:
    image: postgres:15-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres-data:
  redis-data:

Health Checks

Define health checks to ensure services are running properly:

services:
  web:
    image: nginx:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Common Commands

Starting Services

# Start all services
docker-compose up

# Start in detached mode
docker-compose up -d

# Start specific services
docker-compose up web db

# Build images before starting
docker-compose up --build

# Recreate containers
docker-compose up --force-recreate

Stopping Services

# Stop all services
docker-compose stop

# Stop and remove containers
docker-compose down

# Stop and remove containers, volumes, networks
docker-compose down -v

# Stop and remove all resources
docker-compose down --remove-orphans

Viewing Logs

# View all logs
docker-compose logs

# Follow logs in real-time
docker-compose logs -f

# View logs for specific service
docker-compose logs web

# Last 100 lines
docker-compose logs --tail=100

Managing Services

# List running services
docker-compose ps

# Execute command in running container
docker-compose exec web sh

# Run one-off command
docker-compose run web npm test

# Restart service
docker-compose restart web

# Scale service to multiple instances
docker-compose up --scale web=3

Environment Variables

Create a .env file in the same directory as docker-compose.yml:

# .env
POSTGRES_DB=mydb
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret123
DATABASE_URL=postgresql://admin:secret123@db:5432/mydb

Reference in docker-compose.yml:

services:
  db:
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}

Profiles

Use profiles to define service variants:

version: '3.8'

services:
  web:
    image: nginx:latest
    profiles:
      - production

  web-dev:
    image: nginx:latest
    profiles:
      - development

  db:
    image: postgres:15
Usage:
# Start with specific profile
docker-compose --profile production up

# Start all services
docker-compose up

Useful Patterns

Development with Hot Reload

services:
  web:
    build: .
    volumes:
      - ./src:/app/src  # Mount source code
      - /app/node_modules  # Exclude node_modules
    command: npm run dev  # Development server

Override Configuration

Create docker-compose.override.yml (automatically loaded):

# docker-compose.override.yml
version: '3.8'

services:
  web:
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src

Multiple Compose Files

# Base configuration
docker-compose.yml

# Production configuration
docker-compose.prod.yml

# Use multiple files
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up

Troubleshooting

Issue: Port Already in Use

Error: bind: address already in use
Solution: Change port mapping or stop conflicting service

Issue: Container Keeps Restarting

# Check logs
docker-compose logs service-name

# Check container status
docker-compose ps

# Inspect container
docker inspect $(docker-compose ps -q service-name)

Issue: Cannot Connect to Database

💡 Solutions:
  • Ensure depends_on is configured
  • Use service name as hostname (not localhost)
  • Check network configuration
  • Verify environment variables

Best Practices

✅ Do:
  • Use version 3.8 or higher
  • Define networks explicitly
  • Use named volumes for persistence
  • Set up health checks
  • Use environment variables
  • Keep services stateless when possible
  • Document with comments
⚠️ Don't:
  • Store secrets in plain text
  • Use :latest tag in production
  • Expose unnecessary ports
  • Mount entire project directory in production
  • Share credentials via environment files in Git

Next Steps