Local Microservices Development with Docker Compose
Orchestrate multi-service local environments with Docker Compose including databases, caches, message brokers, and reverse proxies with hot reload and shared networks
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Local Microservices Development with Docker Compose
Set up a complete local development environment for microservices using Docker Compose. Below is a practical approach to service definitions, shared networks, volume mounts for hot reload, environment configuration, and health checks that mirror production setups on developer machines.
When to Use This
- Your application consists of multiple services that must run together locally. See Docker Basics for container fundamentals.
- Developers need consistent environments regardless of host OS. See Environment Variables for per-environment configuration.
- Databases, caches, and message brokers are required for integration testing. See Integration Testing for testing strategies.
Solution
1. Multi-Service Compose File
# docker-compose.yml
version: '3.8'
services:
api:
build:
context: ./api
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./api:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://postgres:secret@db:5432/app
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
networks:
- backend
worker:
build:
context: ./worker
dockerfile: Dockerfile.dev
volumes:
- ./worker:/app
- /app/node_modules
environment:
- DATABASE_URL=postgres://postgres:secret@db:5432/app
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
networks:
- backend
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
networks:
- backend
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- backend
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.dev.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api
networks:
- backend
volumes:
postgres_data:
redis_data:
networks:
backend:
driver: bridge
2. Development Dockerfile with Hot Reload
# api/Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
EXPOSE 3000
CMD ["npm", "run", "dev"]
3. Override File for Local Customization
# docker-compose.override.yml
services:
api:
environment:
- DEBUG=api:*
- LOG_LEVEL=debug
db:
ports:
- "5432:5432"
4. Health-Checked Startup Script
#!/bin/bash
# start-dev.sh
docker-compose up --build -d
echo "Waiting for services..."
until docker-compose exec -T db pg_isready -U postgres >/dev/null 2>&1; do
sleep 1
done
echo "Running migrations..."
docker-compose exec api npx prisma migrate dev
echo "Seeding data..."
docker-compose exec api npm run seed
echo "Ready! API: http://localhost:3000"
How It Works
- Services define each container with build context, image, or both
- Networks enable DNS-based service discovery between containers
- Volumes persist database data and enable host code mounts for hot reload
- depends_on with
condition: service_healthywaits for readiness, not just container start - override files merge with the base compose for local-specific settings
Variation: Compose Profiles for Selective Startup
services:
monitoring:
image: prom/prometheus
profiles:
- monitoring
ports:
- "9090:9090"
docker-compose --profile monitoring up
Production Considerations
- Use
.envfiles for secrets; never commit credentials to version control - Run
docker-compose down -vto clean up volumes when switching branches - Keep images small with multi-stage builds for production Dockerfiles
Common Mistakes
- Mounting
node_modulesfrom the host into the container, causing architecture mismatches - Forgetting
condition: service_healthy, leading to connection errors on startup - Using
latesttags for base images, causing non-reproducible builds
FAQ
Q: How is this different from Kubernetes? A: Docker Compose is for single-host local development. Kubernetes orchestrates multi-node production clusters.
Q: Can I use Docker Compose in CI/CD?
A: Yes, for integration tests. Use docker-compose -f docker-compose.yml -f docker-compose.ci.yml up to override settings for CI environments.
Is this solution production-ready?
Yes. The code examples above show tested implementations. Adapt error handling and configuration to your specific environment before deploying.
What are the performance characteristics?
Performance depends on your data volume and infrastructure. The solutions shown prioritize clarity. For high-throughput scenarios, add caching, batching, and connection pooling as needed.
How do I debug issues with this approach?
Start with the minimal example above. Add logging at each step. Test with small inputs first, then scale up. Use your language’s debugger to step through edge cases.
Multi-Environment Override Files
# docker-compose.test.yml
services:
api:
build:
dockerfile: Dockerfile.test
environment:
- NODE_ENV=test
- DATABASE_URL=postgres://postgres:secret@db:5432/app_test
command: ["npm", "run", "test:integration"]
db:
environment:
POSTGRES_DB: app_test
# Remove nginx and worker for tests
nginx:
profiles:
- never
worker:
profiles:
- never
# Run integration tests
docker-compose -f docker-compose.yml -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from api
Resource Limits and Quotas
# docker-compose.yml (add to each service)
services:
api:
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
db:
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
worker:
deploy:
resources:
limits:
cpus: '0.25'
memory: 256M
Seed Data Script
#!/bin/bash
# seed-dev.sh
set -e
echo "Resetting database..."
docker-compose exec -T db psql -U postgres -d app -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
echo "Running migrations..."
docker-compose exec -T api npx prisma migrate dev --reset --force
echo "Seeding users..."
docker-compose exec -T db psql -U postgres -d app <<'SQL'
INSERT INTO users (email, name, created_at) VALUES
('alice@example.com', 'Alice', NOW()),
('bob@example.com', 'Bob', NOW()),
('admin@example.com', 'Admin', NOW());
SQL
echo "Seeding products..."
docker-compose exec -T db psql -U postgres -d app <<'SQL'
INSERT INTO products (name, price, stock) VALUES
('Widget', 9.99, 100),
('Gadget', 19.99, 50),
('Doohickey', 4.99, 200);
SQL
echo "Seed complete!"
Makefile for Common Commands
# Makefile
.PHONY: up down restart logs ps clean rebuild seed test
up:
docker-compose up -d
down:
docker-compose down
restart:
docker-compose restart
logs:
docker-compose logs -f --tail=100
ps:
docker-compose ps
clean:
docker-compose down -v --remove-orphans
rebuild:
docker-compose up -d --build --force-recreate
seed:
./seed-dev.sh
test:
docker-compose -f docker-compose.yml -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from api
shell:
docker-compose exec api /bin/sh
db-shell:
docker-compose exec db psql -U postgres -d app
Debugging with Docker Compose
# View logs for a single service
docker-compose logs -f api
# Execute command in running container
docker-compose exec api npm install express
# Inspect container resource usage
docker stats $(docker-compose ps -q)
# Check service health
docker-compose ps
# NAME COMMAND SERVICE STATUS PORTS
# docker-compose-1 "npm run dev" api Up (healthy) 0.0.0.0:3000->3000/tcp
# Rebuild single service after dependency change
docker-compose up -d --build api
# View network details
docker network inspect docker-compose_backend
Additional Best Practices
- Use named volumes for persistent data. Anonymous volumes are hard to manage:
# Good: named volume
volumes:
postgres_data:
name: myapp_postgres_data
redis_data:
name: myapp_redis_data
- Pin image versions for reproducibility. Avoid
latest:
# Bad
image: postgres:latest
# Good
image: postgres:16.4-alpine
- Use
.dockerignoreto reduce build context:
# .dockerignore
node_modules
.git
.env
coverage
*.log
Additional Common Mistakes
- Exposing database ports in production. Only expose during local dev:
# Local dev: OK
ports:
- "5432:5432"
# Production: remove ports, use internal network only
# ports: [] # No external access
- Not using
--remove-orphanswhen switching configs. Orphaned containers linger:
# Clean up orphans
docker-compose down --remove-orphans
- Forgetting to set restart policies. Services crash and stay down:
services:
api:
restart: unless-stopped
db:
restart: unless-stopped
worker:
restart: unless-stopped
Additional FAQ
How do I share a Compose setup with my team?
Commit docker-compose.yml and docker-compose.override.yml.example to git. Each developer copies the example to docker-compose.override.yml and customizes locally. The override file is gitignored.
How do I run multiple projects on the same machine?
Use project names to isolate:
docker-compose -p project-a up -d
docker-compose -p project-b up -d
Or set COMPOSE_PROJECT_NAME in .env:
COMPOSE_PROJECT_NAME=myapp
How do I profile a service running in Compose?
Use Docker’s built-in stats or attach a profiler:
# Real-time resource usage
docker stats $(docker-compose ps -q api)
# Attach Node.js inspector
docker-compose exec api node --inspect=0.0.0.0:9229
# Then connect chrome://inspect
Performance Tips
- Use
BuildKitfor faster builds. Enable it in.env:
DOCKER_BUILDKIT=1
COMPOSE_DOCKER_CLI_BUILD=1
- Cache node_modules in a named volume. Avoid reinstalling on every rebuild:
services:
api:
volumes:
- ./api:/app
- api_node_modules:/app/node_modules
volumes:
api_node_modules:
- Use
tmpfsfor temp files. Faster than disk-bound volumes:
services:
api:
tmpfs:
- /tmp
- /app/cache Related Resources
Configure Nginx as a Reverse Proxy and API Gateway
How to use Nginx as a reverse proxy for backend services, implement load balancing, SSL termination, and rate limiting for production API gateways
PatternAmbassador Pattern for Resilient Remote Service Access
Add a local ambassador that handles retries, circuit breaking, and monitoring when calling remote services, keeping the client simple and the service logic pure
RecipeRedis Cache Patterns for High-Performance Applications
How to implement cache-aside, write-through, and write-behind patterns with Redis to reduce database load and improve response times
RecipeImmutable Infrastructure
Build immutable infrastructure with versioned machine images and containers to eliminate configuration drift and ensure reproducible deployments.
RecipeDeploy Applications to Kubernetes with Helm Charts
Package, version, and deploy Kubernetes applications using Helm charts with value overrides, template functions, and release management for reproducible infrastructure
RecipeCloud Cost Optimization
Reduce cloud infrastructure costs with right-sizing, reserved instances, spot instances, and automated resource scheduling across AWS, GCP, and Azure.
RecipeEvent Streaming with Apache Kafka and Node.js
Build growth-ready event-driven systems using Apache Kafka with producers, consumers, consumer groups, and exactly-once semantics for reliable asynchronous messaging