Docker Compose Essentials
Quick reference guide for Docker Compose fundamentals.
What is Docker Compose?
Docker Compose is a tool for defining and running multi-container Docker applications:
Installation
macOS and Windows
Docker Compose comes bundled with Docker Desktop.
Linux
# ========== Download Docker Compose ==========
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# ========== Make Executable ==========
sudo chmod +x /usr/local/bin/docker-compose
# ========== Verify Installation ==========
docker-compose --version
Basic Structure
Simple docker-compose.yml
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "80:80"
database:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
Complete Example
version: '3.8'
services:
# Web Application
app:
build: .
container_name: my-app
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://db:5432/myapp
depends_on:
- db
- redis
volumes:
- ./src:/app/src
- node_modules:/app/node_modules
networks:
- app-network
restart: unless-stopped
# Database
db:
image: postgres:15
container_name: postgres-db
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret123
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
ports:
- "5432:5432"
# Cache
redis:
image: redis:7-alpine
container_name: redis-cache
ports:
- "6379:6379"
networks:
- app-network
volumes:
postgres_data:
node_modules:
networks:
app-network:
driver: bridge
Basic Commands
Start and Stop
# ========== Start Services ==========
docker-compose up
# Start in detached mode (background)
docker-compose up -d
# Start specific service
docker-compose up app
# Build and start
docker-compose up --build
# ========== Stop Services ==========
docker-compose down
# Stop and remove volumes
docker-compose down -v
# Stop and remove images
docker-compose down --rmi all
# ========== Stop Without Removing ==========
docker-compose stop
# ========== Start Stopped Services ==========
docker-compose start
Service Management
# ========== View Running Services ==========
docker-compose ps
# ========== View Logs ==========
docker-compose logs
# Follow logs
docker-compose logs -f
# Logs for specific service
docker-compose logs app
# Last 100 lines
docker-compose logs --tail=100
# ========== Execute Commands ==========
docker-compose exec app bash
docker-compose exec db psql -U admin -d myapp
# Run one-off command
docker-compose run app npm test
# ========== Restart Services ==========
docker-compose restart
# Restart specific service
docker-compose restart app
# ========== Scale Services ==========
docker-compose up --scale app=3
Build Configuration
Using Dockerfile
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
NODE_ENV: development
image: myapp:latest
Build with Different Dockerfile
services:
app:
build:
context: ./app
dockerfile: Dockerfile.dev
target: development
Multi-stage Build Target
services:
# Development
app-dev:
build:
context: .
target: development
volumes:
- ./src:/app/src
# Production
app-prod:
build:
context: .
target: production
Environment Variables
Using .env File
# .env
NODE_ENV=production
DATABASE_URL=postgres://db:5432/myapp
SECRET_KEY=your-secret-key
API_PORT=3000
# docker-compose.yml
version: '3.8'
services:
app:
image: myapp
ports:
- "${API_PORT}:3000"
environment:
- NODE_ENV=${NODE_ENV}
- DATABASE_URL=${DATABASE_URL}
- SECRET_KEY=${SECRET_KEY}
Environment File
services:
app:
env_file:
- .env
- .env.local
Inline Environment
services:
app:
environment:
NODE_ENV: production
DATABASE_URL: postgres://db:5432/myapp
DEBUG: "false"
Volumes
Named Volumes
services:
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
driver: local
Bind Mounts
services:
app:
image: myapp
volumes:
# Bind mount (local directory)
- ./src:/app/src
- ./config:/app/config:ro # Read-only
# Named volume
- node_modules:/app/node_modules
Volume Configuration
volumes:
postgres_data:
driver: local
driver_opts:
type: none
o: bind
device: /path/on/host
app_data:
external: true # Use existing volume
Networks
Default Network
# All services automatically join default network
services:
app:
image: myapp
db:
image: postgres
# app can reach db at hostname "db"
Custom Networks
services:
frontend:
image: nginx
networks:
- frontend-net
backend:
image: node
networks:
- frontend-net
- backend-net
database:
image: postgres
networks:
- backend-net
networks:
frontend-net:
backend-net:
Network Configuration
networks:
app-network:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
external-net:
external: true
Health Checks
Container Health Check
services:
app:
image: myapp
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Depends On with Condition
services:
app:
image: myapp
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
Common Patterns
Full-Stack Application
version: '3.8'
services:
# Frontend
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://localhost:8080
volumes:
- ./frontend/src:/app/src
depends_on:
- backend
# Backend API
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://postgres:secret@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- ./backend/src:/app/src
# Database
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: secret
volumes:
- db_data:/var/lib/postgresql/data
# Cache
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
db_data:
Microservices Architecture
version: '3.8'
services:
# API Gateway
gateway:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- user-service
- product-service
# User Service
user-service:
build: ./services/user
environment:
- DATABASE_URL=postgres://db:5432/users
depends_on:
- db
# Product Service
product-service:
build: ./services/product
environment:
- DATABASE_URL=postgres://db:5432/products
depends_on:
- db
# Shared Database
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Development with Hot Reload
version: '3.8'
services:
app:
build:
context: .
target: development
ports:
- "3000:3000"
volumes:
# Mount source code for hot reload
- ./src:/app/src
- ./package.json:/app/package.json
# Exclude node_modules
- /app/node_modules
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true # For file watching
command: npm run dev
Override Files
docker-compose.override.yml
# Base: docker-compose.yml
version: '3.8'
services:
app:
image: myapp
environment:
- NODE_ENV=production
# Development override (automatically used)
# docker-compose.override.yml
version: '3.8'
services:
app:
environment:
- NODE_ENV=development
- DEBUG=true
volumes:
- ./src:/app/src
Multiple Compose Files
# ========== Production ==========
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
# ========== Staging ==========
docker-compose -f docker-compose.yml -f docker-compose.staging.yml up
# ========== Development (uses override automatically) ==========
docker-compose up
Example: docker-compose.prod.yml
version: '3.8'
services:
app:
image: myapp:1.0.0
restart: always
environment:
- NODE_ENV=production
# No volume mounts for production
db:
restart: always
# Use external managed database
# environment:
# DATABASE_URL: ${PROD_DATABASE_URL}
Database Initialization
PostgreSQL Init Script
services:
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
-- init-scripts/01-schema.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255) UNIQUE
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
total DECIMAL(10, 2)
);
MySQL Init Script
services:
db:
image: mysql:8
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: myapp
MYSQL_USER: user
MYSQL_PASSWORD: password
Service Discovery
Container Communication
services:
app:
image: myapp
environment:
# Use service name as hostname
- DATABASE_URL=postgres://db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
db:
image: postgres:15
# Accessible at hostname "db"
cache:
image: redis:7
# Accessible at hostname "cache"
Network Aliases
services:
db:
image: postgres:15
networks:
backend:
aliases:
- database
- postgres-db
- primary-db
networks:
backend:
Resource Limits
CPU and Memory Limits
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
For Compose v2
services:
app:
image: myapp
mem_limit: 512m
mem_reservation: 256m
cpus: 0.5
Profiles
Conditional Service Start
services:
app:
image: myapp
# Always starts
debug-tools:
image: debug-image
profiles: ["debug"]
monitoring:
image: prometheus
profiles: ["monitoring", "production"]
# ========== Start with Profile ==========
docker-compose --profile debug up
# Multiple profiles
docker-compose --profile debug --profile monitoring up
# Without profile (only app starts)
docker-compose up
Best Practices
1. Version Control
# .gitignore
.env
.env.local
*.env.local
docker-compose.override.yml
# .env.example (commit this)
NODE_ENV=development
DATABASE_URL=postgres://db:5432/myapp
SECRET_KEY=your-secret-here
2. Service Names
# Use descriptive service names
services:
web-frontend:
# ...
api-backend:
# ...
postgres-database:
# ...
3. Health Checks
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
4. Restart Policies
services:
app:
restart: unless-stopped # or always, on-failure, no
5. Resource Management
services:
app:
deploy:
resources:
limits:
memory: 1G
Troubleshooting
View Logs
# ========== All Services ==========
docker-compose logs
# ========== Specific Service ==========
docker-compose logs app
# ========== Follow Logs ==========
docker-compose logs -f
# ========== Last N Lines ==========
docker-compose logs --tail=50
# ========== With Timestamps ==========
docker-compose logs -t
Debug Container
# ========== Execute Shell ==========
docker-compose exec app sh
# ========== Check Process ==========
docker-compose exec app ps aux
# ========== Check Network ==========
docker-compose exec app ping db
# ========== View Environment ==========
docker-compose exec app env
Cleanup
# ========== Remove Stopped Containers ==========
docker-compose down
# ========== Remove Volumes ==========
docker-compose down -v
# ========== Remove Everything ==========
docker-compose down -v --rmi all --remove-orphans
# ========== Prune Unused Resources ==========
docker system prune -a
Common Issues
# ========== Port Already in Use ==========
# Change port mapping
# ports:
# - "8080:3000" # Use different host port
# Or stop conflicting service
lsof -ti:3000 | xargs kill
# ========== Volume Permission Issues ==========
# Fix volume permissions
docker-compose exec app chown -R node:node /app
# ========== Service Not Starting ==========
# Check logs
docker-compose logs app
# Build without cache
docker-compose build --no-cache app
# ========== Network Issues ==========
# Recreate network
docker-compose down
docker-compose up
Advanced Features
Extensions
x-common-variables: &common-vars
NODE_ENV: production
LOG_LEVEL: info
services:
app1:
image: myapp
environment:
<<: *common-vars
SERVICE_NAME: app1
app2:
image: myapp
environment:
<<: *common-vars
SERVICE_NAME: app2
Wait for Services
services:
app:
image: myapp
depends_on:
db:
condition: service_healthy
command: >
sh -c "
echo 'Waiting for database...' &&
sleep 5 &&
npm start
"
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
Build Arguments
services:
app:
build:
context: .
args:
- NODE_VERSION=18
- BUILD_DATE=${BUILD_DATE}
Tips
Common Commands Reference
# ========== Lifecycle ==========
docker-compose up -d # Start in background
docker-compose down # Stop and remove
docker-compose restart # Restart services
docker-compose stop # Stop without removing
docker-compose start # Start stopped services
# ========== Build ==========
docker-compose build # Build images
docker-compose build --no-cache # Build without cache
docker-compose up --build # Build and start
# ========== Logs ==========
docker-compose logs -f # Follow logs
docker-compose logs app # Service-specific logs
# ========== Execute ==========
docker-compose exec app bash # Open shell
docker-compose run app npm test # Run command
# ========== Management ==========
docker-compose ps # List services
docker-compose top # Display processes
docker-compose config # Validate and view config
# ========== Cleanup ==========
docker-compose down -v # Remove volumes
docker-compose down --rmi all # Remove images