Docker Compose Is Underrated
Most developers use Docker Compose for the basics: spin up a database and cache alongside their app. But Compose has features that can make your local dev environment much more powerful.
1. Health Checks + depends_on Conditions
The classic problem: your app starts before the database is ready and crashes.
services:
db:
image: postgres:16
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 5s
timeout: 3s
retries: 5
app:
build: .
depends_on:
db:
condition: service_healthy # Wait for healthy, not just started
environment:
DATABASE_URL: postgres://user:password@db:5432/myapp
service_healthy waits for the health check to pass, not just for the container to start.
2. Override Files for Environment Differences
Use multiple Compose files for different environments:
# docker-compose.yml โ base config shared everywhere
services:
app:
build: .
environment:
NODE_ENV: development
db:
image: postgres:16
# docker-compose.override.yml โ auto-loaded in development
services:
app:
volumes:
- .:/app # Mount source for hot reload
- /app/node_modules # Preserve container's node_modules
ports:
- "3000:3000"
environment:
DEBUG: "true"
# docker-compose.prod.yml โ used explicitly in CI/staging
services:
app:
image: myapp:${VERSION} # Use pre-built image, not build
restart: always
# Development (uses base + override automatically):
docker compose up
# Production:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
3. Profiles for Optional Services
Not everyone needs every service. Use profiles:
services:
app:
build: .
# No profile = always starts
db:
image: postgres:16
# No profile = always starts
redis:
image: redis:7
profiles: ["cache"] # Only starts with --profile cache
mailhog:
image: mailhog/mailhog
profiles: ["email"] # Only starts with --profile email
prometheus:
image: prom/prometheus
profiles: ["monitoring"]
# Default: just app and db
docker compose up
# With email testing
docker compose --profile email up
# With everything
docker compose --profile cache --profile email --profile monitoring up
4. Volume for Faster Node Modules
Mounting your source code for hot reload but keeping node_modules in the container:
services:
app:
build: .
volumes:
- .:/app # Mount source code
- node_modules:/app/node_modules # Named volume for deps
volumes:
node_modules: # Declare the named volume
The named volume is managed by Docker, persists between restarts, and isn't overwritten by the source mount.
5. Watch Mode (Compose Watch)
Docker Compose Watch (introduced in Compose 2.22) syncs file changes without volume mounts โ faster and more selective:
services:
app:
build: .
develop:
watch:
- action: sync
path: ./src
target: /app/src
ignore:
- node_modules
- action: rebuild
path: package.json # Rebuild when deps change
docker compose watch
Changes to src/ are synced instantly. Changes to package.json trigger a rebuild. No volume mount overhead.
6. Use .env Files Properly
# .env (default, loaded automatically)
DATABASE_URL=postgres://user:pass@db:5432/myapp
REDIS_URL=redis://redis:6379
# .env.local (overrides for your machine, gitignored)
API_KEY=dev-key-abc123
# docker-compose.yml
services:
app:
env_file:
- .env
- .env.local # Overrides .env values
Add .env.local to .gitignore. Commit .env with safe defaults for local development only.
7. Useful Aliases
Add to your ~/.bashrc or ~/.zshrc:
alias dc='docker compose'
alias dcu='docker compose up -d'
alias dcd='docker compose down'
alias dcl='docker compose logs -f'
alias dcps='docker compose ps'
alias dcr='docker compose restart'
# Shortcut: nuke and restart fresh
alias dcfresh='docker compose down -v && docker compose up -d'
Key Takeaways
- Use
depends_on: condition: service_healthyโ wait for databases to be ready - Override files (
docker-compose.override.yml) โ different config per environment, no duplication - Profiles โ optional services that don't clutter default
up - Named volume for
node_modulesโ avoid the source mount overwriting container deps - Compose Watch โ file sync without volume mount performance overhead
.env+.env.localโ shared defaults with per-machine overrides