Docker & Kubernetes Complete Guide Part 4: Multi-Container Management with Docker Compose
Master docker-compose.yml, Service Definitions, Networks, and Practical Examples
Introduction: The Need for Docker Compose
Real-world applications typically consist of multiple services. Various components such as web servers, databases, caches, and message queues must work together. Managing such multi-container environments with individual docker run commands is cumbersome and error-prone.
Docker Compose solves this problem by allowing you to define multiple containers in a single YAML file and manage the entire application stack with a single command.
1. What is Docker Compose?
1.1 Definition of Docker Compose
Docker Compose is a tool for defining and running multi-container Docker applications. You declaratively define your application's services, networks, and volumes in a docker-compose.yml file.
Key features of Docker Compose:
- Declarative Definition: Define entire application stack in YAML file
- Single Command Management: Start all services with
docker compose up - Environment Isolation: Creates independent network per project
- Variable Support: Flexible configuration with environment variables and
.envfiles - Development Convenience: Especially useful for local development environment setup
1.2 Docker Compose V2
Docker Desktop and latest Docker Engine include Compose V2 by default. V2 is rewritten in Go and uses the format docker compose (without hyphen).
# V1 (legacy)
docker-compose up
# V2 (recommended)
docker compose up
2. docker-compose.yml Structure
2.1 Basic Structure
# docker-compose.yml
version: "3.9" # Compose file version (optional)
services: # Service (container) definitions
web:
image: nginx:alpine
ports:
- "80:80"
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes: # Volume definitions (optional)
db-data:
networks: # Network definitions (optional)
backend:
2.2 Minimal Configuration Example
# Simplest docker-compose.yml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
3. Service Definition Details
3.1 image - Specify Image
services:
web:
# Docker Hub image
image: nginx:alpine
db:
# Registry image
image: registry.example.com/mydb:1.0
app:
# Specific digest
image: myapp@sha256:abc123...
3.2 build - Build Image
services:
app:
# Simple build
build: .
api:
# Detailed build options
build:
context: ./api
dockerfile: Dockerfile.prod
args:
VERSION: "2.0"
BUILD_ENV: production
target: production # Multi-stage build target
frontend:
# Tag image after build
build: ./frontend
image: myapp/frontend:latest
3.3 ports - Port Mapping
services:
web:
image: nginx
ports:
# host:container
- "80:80"
- "443:443"
# Auto-assign host port
- "80"
# Bind to specific IP only
- "127.0.0.1:3000:3000"
# UDP port
- "53:53/udp"
# Long format
- target: 80
published: 8080
protocol: tcp
mode: host
3.4 volumes - Volume Mounts
services:
db:
image: postgres:15
volumes:
# Named Volume
- db-data:/var/lib/postgresql/data
# Bind Mount (host path)
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
# Read-only mount
- ./config:/etc/app/config:ro
# Long format
- type: volume
source: db-data
target: /var/lib/postgresql/data
volume:
nocopy: true
volumes:
db-data: # Volume declaration
cache-data:
driver: local
3.5 environment - Environment Variables
services:
app:
image: myapp
environment:
# Map format
NODE_ENV: production
DEBUG: "false"
DATABASE_URL: postgres://user:pass@db:5432/mydb
db:
image: postgres
environment:
# Array format
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=myapp
worker:
image: myworker
# Load environment variables from file
env_file:
- .env
- .env.local
3.6 command and entrypoint
services:
app:
image: node:20
# Override default command
command: npm run dev
worker:
image: python:3.11
# Array format
command: ["python", "-m", "celery", "worker"]
custom:
image: alpine
# Override entrypoint
entrypoint: /custom-entrypoint.sh
command: ["--config", "/etc/app/config.yml"]
4. Dependency Management (depends_on)
4.1 Basic Dependencies
services:
web:
image: nginx
depends_on:
- api
- db
api:
image: myapi
depends_on:
- db
- redis
db:
image: postgres:15
redis:
image: redis:alpine
4.2 Conditional Dependencies (service_healthy)
services:
web:
image: nginx
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
Important: depends_on only guarantees startup order, not that the service is "ready". In production, use healthcheck with condition: service_healthy.
5. Network Configuration
5.1 Default Network Behavior
Docker Compose automatically creates a default network for the project. Services on the same network can communicate with each other using service names.
services:
web:
image: nginx
# Can access db service via hostname "db"
db:
image: postgres
# Can access web service via hostname "web"
5.2 Custom Networks
services:
frontend:
image: nginx
networks:
- frontend-net
api:
image: myapi
networks:
- frontend-net
- backend-net
db:
image: postgres
networks:
- backend-net
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
internal: true # Block external access
6. Practical Example: WordPress + MySQL
# wordpress/docker-compose.yml
services:
wordpress:
image: wordpress:latest
restart: unless-stopped
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress_password
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress-data:/var/www/html
depends_on:
db:
condition: service_healthy
networks:
- wordpress-net
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress_password
MYSQL_ROOT_PASSWORD: root_password
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- wordpress-net
volumes:
wordpress-data:
db-data:
networks:
wordpress-net:
driver: bridge
7. Practical Example: Node.js + MongoDB + Redis
# nodejs-stack/docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
ports:
- "3000:3000"
environment:
NODE_ENV: development
MONGODB_URI: mongodb://mongo:27017/myapp
REDIS_URL: redis://redis:6379
volumes:
- .:/app
- /app/node_modules
depends_on:
mongo:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-net
command: npm run dev
mongo:
image: mongo:7
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
MONGO_INITDB_DATABASE: myapp
volumes:
- mongo-data:/data/db
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- app-net
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
- app-net
volumes:
mongo-data:
redis-data:
networks:
app-net:
driver: bridge
8. Docker Compose Commands
8.1 Basic Commands
# Start services (background)
docker compose up -d
# Start services (foreground, show logs)
docker compose up
# Build images then start
docker compose up --build
# Start specific services only
docker compose up -d web db
# Stop and remove services
docker compose down
# Also remove volumes
docker compose down -v
# Also remove images
docker compose down --rmi all
8.2 Status Check Commands
# List running services
docker compose ps
# All services (including stopped)
docker compose ps -a
# Check service logs
docker compose logs
# Specific service logs
docker compose logs web
# Real-time log streaming
docker compose logs -f
# Last 100 lines only
docker compose logs --tail=100
8.3 Service Management Commands
# Restart services
docker compose restart
docker compose restart web
# Stop services (keep containers)
docker compose stop
# Start stopped services
docker compose start
# Scale services
docker compose up -d --scale web=3
# Apply configuration changes
docker compose up -d --force-recreate
8.4 Execute Commands in Container (exec)
# Execute command in container
docker compose exec web sh
docker compose exec db psql -U postgres
# Execute as specific user
docker compose exec --user root web sh
# Run with new container (run)
docker compose run --rm app npm test
9. Environment-Specific Configuration Separation
9.1 Using .env File
# .env file
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret123
APP_PORT=3000
NODE_ENV=development
# docker-compose.yml
services:
app:
ports:
- "${APP_PORT}:3000"
environment:
NODE_ENV: ${NODE_ENV}
db:
image: postgres:15
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
9.2 Using Multiple Compose Files
# docker-compose.yml (base configuration)
services:
app:
build: .
environment:
NODE_ENV: production
db:
image: postgres:15
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
# docker-compose.override.yml (development, auto-applied)
services:
app:
build:
context: .
target: development
volumes:
- .:/app
environment:
NODE_ENV: development
ports:
- "3000:3000"
db:
ports:
- "5432:5432"
# Development environment (base + override auto-merged)
docker compose up
# Production environment
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
9.3 Using Profiles (Optional Services)
services:
app:
image: myapp
db:
image: postgres:15
# Only for development
adminer:
image: adminer
profiles:
- debug
ports:
- "8080:8080"
# Only for testing
test-runner:
image: myapp-test
profiles:
- test
# Start default services only
docker compose up
# Include debug profile
docker compose --profile debug up
# Multiple profiles
docker compose --profile debug --profile test up
10. Useful Configuration Options
10.1 restart Policy
services:
app:
image: myapp
restart: "no" # Don't restart (default)
# restart: always # Always restart
# restart: on-failure # Restart only on failure
# restart: unless-stopped # Restart until manually stopped
10.2 Resource Limits
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: "0.5"
memory: 512M
reservations:
cpus: "0.25"
memory: 256M
Conclusion
Docker Compose is an essential tool for multi-container application management. Summary of topics covered:
- docker-compose.yml Structure: Roles and definitions of services, volumes, networks
- Service Definition: Core settings including image, build, ports, volumes, environment
- Dependency Management: Reliable startup order with depends_on and healthcheck
- Networks: Inter-service communication and network isolation
- Practical Examples: Real use cases like WordPress, Node.js stack
- Commands: Key commands including up, down, ps, logs, exec
- Environment-Specific Config: Using .env files, override files, profiles
In the next part, we will cover Docker Networks and Volumes in depth, exploring container networking and persistent storage in detail.