Docker Compose — Multi-Container Apps Guide

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

Docker Compose — Multi-Container Apps Guide

Docker Compose simplifies running multiple interconnected containers. Learn to orchestrate complex applications with a single docker-compose.yml file.

Introduction

Docker Compose uses YAML to define multi-container applications. Instead of running multiple docker run commands, you define services, networks, and volumes in a configuration file, then start everything with one command.

Installation

Docker Compose comes bundled with Docker Desktop. For 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
docker-compose --version

Basic Compose File Structure

version: '3.8'

services:
  web:
    image: myapp:1.0
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production

  db:
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=secret

volumes:
  pgdata:

networks:
  default:
    driver: bridge

Services Configuration

Building from Dockerfile

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"

Using Pre-built Images

services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Combining Build and Image

services:
  app:
    build: ./app
    image: myapp:latest
    ports:
      - "3000:3000"

Environment Variables and Configuration

services:
  web:
    image: myapp:1.0
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://db/app
      - REDIS_URL=redis://cache:6379
    env_file:
      - .env
    ports:
      - "3000:3000"

Create .env file:

NODE_ENV=development
DEBUG=true
LOG_LEVEL=info

Volumes and Data Persistence

version: '3.8'

services:
  db:
    image: postgres:15
    volumes:
      # Named volume
      - db_data:/var/lib/postgresql/data
      # Bind mount
      - ./init-scripts:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_PASSWORD=secret

  app:
    image: myapp:1.0
    volumes:
      # Bind mount for development
      - ./src:/app/src
      # Mount logs
      - app_logs:/app/logs

volumes:
  db_data:
  app_logs:

Networking

Services in Compose can communicate using service names:

version: '3.8'

services:
  web:
    image: myapp:1.0
    ports:
      - "3000:3000"
    depends_on:
      - db
      - cache

  db:
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=secret

  cache:
    image: redis:7-alpine

networks:
  default:
    driver: bridge

Code inside web container:

// Connect using service name as hostname
const db = require('pg').Pool({
  host: 'db',  // Service name
  port: 5432,
  user: 'postgres',
  password: 'secret',
  database: 'app'
});

const redis = require('redis').createClient({
  url: 'redis://cache:6379'  // Service name
});

Complete Production Example

version: '3.8'

services:
  web:
    build: ./app
    container_name: web_app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/app
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - app_network
    volumes:
      - app_logs:/app/logs

  db:
    image: postgres:15-alpine
    container_name: postgres_db
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=app
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    restart: unless-stopped
    networks:
      - app_network

  cache:
    image: redis:7-alpine
    container_name: redis_cache
    ports:
      - "6379:6379"
    restart: unless-stopped
    networks:
      - app_network

volumes:
  db_data:
  app_logs:

networks:
  app_network:
    driver: bridge

Common Commands

# Start services in foreground
docker-compose up

# Start in background
docker-compose up -d

# Stop services
docker-compose down

# Stop and remove volumes
docker-compose down -v

# View logs
docker-compose logs

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

# Execute command in service
docker-compose exec db psql -U user -d app

# Rebuild images
docker-compose build

# List services
docker-compose ps

# Restart services
docker-compose restart

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

Development vs Production

Development (docker-compose.yml)

services:
  web:
    build: .
    volumes:
      - .:/app  # Live reload
    ports:
      - "3000:3000"
    environment:
      - DEBUG=true

Production (docker-compose.prod.yml)

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
services:
  web:
    image: registry.example.com/myapp:1.0.0
    restart: always
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]

Troubleshooting

# View detailed service information
docker-compose ps -a

# Inspect service logs
docker-compose logs web --tail=100

# Check network connectivity
docker-compose exec web ping db

# View resource usage
docker-compose stats

# Restart specific service
docker-compose restart db

# Remove stopped containers
docker-compose down

Best Practices

  1. Use named volumes for persistent data
  2. Set restart policies for production
  3. Define health checks for reliability
  4. Use .env files for secrets
  5. Pin image versions (not latest)
  6. Separate dev and prod configs
  7. Document dependencies with depends_on

FAQ

Q: How do I pass secrets securely? A: Use .env files for local development and Docker secrets or environment variables for production. Never commit secrets to version control.

Q: Can I use Docker Compose for production? A: Docker Compose is suitable for single-host production setups. For multi-host, use Kubernetes or Docker Swarm.

Q: How do I rebuild a specific service? A: Use docker-compose build web && docker-compose up -d to rebuild and restart a specific service.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro