Docker Compose for Local Development of Microservices
Running one service locally is easy. Running ten—with their databases, message queues, and dependencies—is a nightmare. Docker Compose solves this.
The Problem
Your microservices architecture:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ API │ ──▶ │ Users │ ──▶ │ Postgres│
│ Gateway │ │ Service │ └─────────┘
└─────────┘ └─────────┘
│
▼
┌─────────┐ ┌─────────┐
│ Orders │ ──▶ │ Redis │
│ Service │ └─────────┘
└─────────┘
│
▼
┌─────────┐
│ RabbitMQ│
└─────────┘
Running this locally means: 4 services, 3 databases/queues, environment variables, network configuration…
Docker Compose to the Rescue
# docker-compose.yml
version: '3.8'
services:
api-gateway:
build: ./api-gateway
ports:
- "8000:8000"
environment:
- USERS_SERVICE_URL=http://users-service:8001
- ORDERS_SERVICE_URL=http://orders-service:8002
depends_on:
- users-service
- orders-service
users-service:
build: ./users-service
ports:
- "8001:8001"
environment:
- DATABASE_URL=postgres://user:pass@postgres:5432/users
depends_on:
- postgres
orders-service:
build: ./orders-service
ports:
- "8002:8002"
environment:
- REDIS_URL=redis://redis:6379
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- redis
- rabbitmq
postgres:
image: postgres:13
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=users
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:6
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
volumes:
postgres_data:
One command: docker-compose up
Development Workflow
Starting Everything
# Start all services
docker-compose up
# Start in background
docker-compose up -d
# Start specific services
docker-compose up api-gateway users-service
Hot Reload with Volumes
services:
users-service:
build: ./users-service
volumes:
- ./users-service:/app # Mount source code
- /app/node_modules # Exclude node_modules
command: npm run dev # Run dev server with watch
Edit code locally, changes reflected immediately.
Viewing Logs
# All logs
docker-compose logs -f
# Specific service
docker-compose logs -f users-service
# Last 100 lines
docker-compose logs --tail=100 users-service
Running Commands
# Run in new container
docker-compose run users-service npm test
# Run in existing container
docker-compose exec users-service bash
# Database migrations
docker-compose exec users-service python manage.py migrate
Development vs Production
Override Files
# docker-compose.yml (base)
services:
api:
build: ./api
# docker-compose.override.yml (dev, auto-loaded)
services:
api:
volumes:
- ./api:/app
environment:
- DEBUG=true
command: npm run dev
# docker-compose.prod.yml (production)
services:
api:
environment:
- DEBUG=false
command: npm start
# Dev (uses override automatically)
docker-compose up
# Prod
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
Service Discovery
Docker Compose creates a network where services can reach each other by name:
# In orders-service
import redis
r = redis.Redis(host='redis', port=6379) # 'redis' is the service name
Health Checks
services:
postgres:
image: postgres:13
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
users-service:
build: ./users-service
depends_on:
postgres:
condition: service_healthy
Service starts only when dependency is healthy.
Environment Variables
.env File
# .env
POSTGRES_PASSWORD=supersecret
API_KEY=abc123
# docker-compose.yml
services:
api:
environment:
- API_KEY=${API_KEY}
Multiple Environments
# .env.development
DATABASE_URL=postgres://localhost/dev
# .env.test
DATABASE_URL=postgres://localhost/test
docker-compose --env-file .env.test up
Networking
Custom Networks
services:
frontend:
networks:
- frontend
api:
networks:
- frontend
- backend
database:
networks:
- backend
networks:
frontend:
backend:
Database not accessible from frontend.
Profiles
Group services for different workflows:
services:
api:
# Always starts
frontend:
profiles: ["full"]
test-runner:
profiles: ["test"]
# Just API
docker-compose up
# Full stack
docker-compose --profile full up
# Testing
docker-compose --profile test up
Performance Tips
Build Caching
# Order matters for cache
COPY package*.json ./
RUN npm install # Cached if package.json unchanged
COPY . . # Invalidates after this
Parallel Builds
docker-compose build --parallel
Local Development Database
services:
postgres:
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
Seed data on first start.
Common Issues
Port Conflicts
Error: port 5432 already allocated
Check for local PostgreSQL: lsof -i :5432
Dependency Timing
depends_on waits for container start, not service ready. Use health checks.
Volume Permissions
# In Dockerfile
RUN chown -R node:node /app
USER node
Final Thoughts
Docker Compose isn’t production orchestration—use Kubernetes for that. But for local development, it’s essential.
Start with infrastructure services (databases, queues). Add application services. Use volumes for hot reload. Test the full stack locally before pushing.
Develop locally like you deploy: containerized.