Docker Compose shines for local development. Spin up your entire stack — application, database, cache, message queue — with one command. But there’s a difference between a Compose setup that works and one that’s pleasant to use.
Project Structure
project/
├── docker-compose.yml # Core services
├── docker-compose.dev.yml # Dev overrides (hot reload, debug ports)
├── .env # Environment variables
├── Dockerfile # Production build
└── Dockerfile.dev # Dev build (with hot reload)
Separate your dev configuration from production. Use override files:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
Hot Reload with Volume Mounts
The key to a good dev experience is live code reloading:
# docker-compose.dev.yml
services:
api:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- ./src:/app/src
- /app/node_modules # Don't mount node_modules from host
environment:
- NODE_ENV=development
Mount your source code but exclude node_modules. This avoids platform mismatches (native modules built for macOS won’t work in a Linux container).
Health Checks and Dependencies
Don’t rely on depends_on alone. It only waits for the container to start, not for the service to be ready:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
api:
depends_on:
db:
condition: service_healthy
Now the API container waits until PostgreSQL is actually accepting connections.
Named Volumes for Data Persistence
volumes:
postgres_data:
redis_data:
services:
db:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data
Named volumes persist across docker compose down. Your database data survives restarts. Use docker compose down -v when you explicitly want a clean slate.
Development Seed Data
Add an init script for your database:
services:
db:
image: postgres:16
volumes:
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
Every team member gets the same starting data. No more “it works on my machine” because someone forgot to run migrations.
Useful Compose Commands
# Start in background
docker compose up -d
# View logs for one service
docker compose logs -f api
# Run a one-off command
docker compose exec api npm run migrate
# Rebuild after dependency changes
docker compose up --build api
# Clean everything
docker compose down -v --remove-orphans
A well-configured Docker Compose setup eliminates the “20 steps to set up the dev environment” README. New team members run one command and they’re productive.