Docker Compose for Development 2026: Multi-Container Setups
Master Docker Compose for development environments. Learn multi-container setups, development workflows, service orchestration, and best practices.
Full Stack Developer | DevOps Expert
Introduction to Docker Compose
Docker Compose simplifies multi-container Docker applications. It's perfect for development environments, allowing you to define and run multi-container applications with a single command.
This guide covers everything from basic setups to advanced multi-container orchestration for development workflows.
Basic Docker Compose Setup
Get started with a simple Docker Compose configuration for a web application.
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
command: npm run dev
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
# Run with
docker-compose up
docker-compose up -d # detached mode
docker-compose down # stop and removeMulti-Container Application
Set up a complete application with web server, database, and cache.
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
# Network configuration
networks:
default:
driver: bridgeDevelopment Workflow
Optimize your development workflow with Docker Compose commands and patterns.
# Development commands
docker-compose up # Start all services
docker-compose up -d # Start in detached mode
docker-compose down # Stop and remove containers
docker-compose logs # View logs
docker-compose logs -f web # Follow logs for specific service
docker-compose ps # List running containers
docker-compose exec web bash # Execute command in container
docker-compose restart web # Restart specific service
docker-compose build # Rebuild images
docker-compose pull # Pull latest images
# Development override
# docker-compose.dev.yml
version: '3.8'
services:
web:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
command: npm run dev
# Run with override
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up
# Hot reload configuration
# Dockerfile
FROM node:18-alpine AS base
WORKDIR /app
FROM base AS development
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS production
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["npm", "start"]Health Checks and Dependencies
Implement health checks to ensure services are ready before dependent services start.
# docker-compose.yml with health checks
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Wait script for dependencies
# wait-for-it.sh
#!/bin/sh
TIMEOUT=15
QUIET=0
HOST=$1
PORT=$2
shift 2
COMMAND=$@
if [ "$QUIET" -ne 1 ]; then
echo "Waiting for $HOST:$PORT..."
end
for i in $(seq 1 $TIMEOUT); do
nc -z "$HOST" "$PORT" >/dev/null 2>&1
if [ $? -eq 0 ]; then
if [ "$QUIET" -ne 1 ]; then
echo "$HOST:$PORT is available"
fi
exec $COMMAND
fi
sleep 1
done
echo "Timeout reached for $HOST:$PORT"
exit 1}Environment Configuration
Manage environment variables across different environments with Docker Compose.
# .env file
DATABASE_URL=postgresql://user:password@db:5432/myapp
REDIS_URL=redis://redis:6379
API_KEY=your_api_key
NODE_ENV=development
# .env.production
DATABASE_URL=postgresql://prod_user:prod_pass@prod_db:5432/myapp
REDIS_URL=redis://prod_redis:6379
API_KEY=prod_api_key
NODE_ENV=production
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
env_file:
- .env
environment:
- NODE_ENV=${NODE_ENV:-development}
# Using different env files
docker-compose --env-file .env.production up
# Environment substitution
version: '3.8'
services:
web:
image: nginx:${NGINX_VERSION:-latest}
ports:
- "${WEB_PORT:-80}:80"
environment:
- APP_NAME=${APP_NAME}
- DEBUG=${DEBUG:-false}Volumes and Networking
Configure volumes for data persistence and custom networks for service communication.
# docker-compose.yml with volumes and networks
version: '3.8'
services:
web:
build: .
volumes:
- ./src:/app/src # Bind mount for development
- node_modules:/app/node_modules # Named volume for dependencies
- ./logs:/app/logs # Log directory
networks:
- frontend
- backend
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- backend
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
networks:
- backend
volumes:
postgres_data:
driver: local
redis_data:
driver: local
node_modules:
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external access
# Volume backup and restore
# Backup
docker run --rm -v myapp_postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz /data
# Restore
docker run --rm -v myapp_postgres_data:/data -v $(pwd):/backup alpine tar xzf /backup/postgres_backup.tar.gz -C /Production Considerations
Best practices for using Docker Compose in production environments.
# docker-compose.prod.yml
version: '3.8'
services:
web:
image: myapp:latest
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
db:
image: postgres:15-alpine
deploy:
resources:
limits:
memory: 1G
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
# Security best practices
# Use non-root users
# Dockerfile
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
USER nodejs
WORKDIR /app
# Scan images for vulnerabilities
docker scan myapp:latest
# Use specific image versions
# Instead of latest
image: postgres:15-alpine
# Limit container capabilities
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# Read-only filesystem
read_only: true
tmpfs:
- /tmpDocker Compose Best Practices
Development
- • Use bind mounts for code
- • Enable hot reload
- • Separate dev and prod configs
- • Use environment variables
Performance
- • Use named volumes for data
- • Optimize Dockerfile layers
- • Use .dockerignore
- • Cache dependencies
Security
- • Scan images for vulnerabilities
- • Use specific image versions
- • Run as non-root user
- • Limit container capabilities
Maintenance
- • Version control compose files
- • Document service dependencies
- • Use health checks
- • Monitor resource usage
Conclusion
Docker Compose is an essential tool for development environments, making it easy to manage multi-container applications. By following these patterns and best practices, you can create efficient, reproducible development setups.
Start with simple setups and gradually add complexity as your application grows. Remember to separate development and production configurations for optimal results.
Ready to Containerize Your Development?
Explore more DevOps tutorials and streamline your development workflow!