Docker Compose — Multi-Container Apps Guide
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.
- Docker Compose — Multi-Container Apps Guide
- Installation
- Basic Compose File Structure
- Services Configuration
- Building from Dockerfile
- Using Pre-built Images
- Combining Build and Image
- Environment Variables and Configuration
- Volumes and Data Persistence
- Networking
- Complete Production Example
- Common Commands
- Development vs Production
- Development (docker-compose.yml)
- Production (docker-compose.prod.yml)
- Troubleshooting
- Best Practices
- FAQ
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
- Use named volumes for persistent data
- Set restart policies for production
- Define health checks for reliability
- Use .env files for secrets
- Pin image versions (not
latest) - Separate dev and prod configs
- 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