01The Multi-Environment Problem
A common mistake is maintaining separate docker-compose files for each environment — docker-compose.dev.yml, docker-compose.staging.yml, docker-compose.prod.yml. They start identical but gradually drift apart as changes are made to one but not the others. Eventually, a bug exists in production that can't be reproduced in development because the configurations are subtly different.
Docker Compose has built-in features to solve this: override files and profiles. The pattern is simple: one base file defines the shared configuration, and small override files define only the differences. Let me show you how.
02Compose Override Files
Docker Compose automatically merges docker-compose.yml with docker-compose.override.yml if both exist. For multiple environments, use the -f flag to specify which files to merge:
[docker-compose.yml]
1# docker-compose.yml (base - shared across all environments)2services: 3 app: 4 image: myapp:latest5 depends_on: 6 db: 7 condition: service_healthy89 db: 10 image: postgres:16-alpine11 volumes: 12 - pgdata:/var/lib/postgresql/data13 healthcheck: 14 test: ["CMD-SHELL", "pg_isready"]15 interval: 5s16 retries: 51718volumes: 19 pgdata: 03Development Override
The dev override adds hot-reloading, debug ports, and relaxed resource limits:
[docker-compose.dev.yml]
1# docker-compose.dev.yml2services: 3 app: 4 build: .5 volumes: 6 - ./src:/app/src # Hot reload7 ports: 8 - "3000:3000"9 - "9229:9229" # Debug port10 environment: 11 - NODE_ENV=development12 - DEBUG=true1314 db: 15 ports: 16 - "5432:5432" # Expose for local tools17 environment: 18 - POSTGRES_PASSWORD=devpassword04Production Override
The prod override adds resource limits, security hardening, and production-specific config:
[docker-compose.prod.yml]
1# docker-compose.prod.yml2services: 3 app: 4 restart: unless-stopped5 environment: 6 - NODE_ENV=production7 deploy: 8 resources: 9 limits: 10 memory: 1G11 cpus: "2.0"12 read_only: true13 security_opt: 14 - no-new-privileges:true1516 db: 17 restart: unless-stopped18 environment: 19 - POSTGRES_PASSWORD_FILE=/run/secrets/db_password20 secrets: 21 - db_password22 deploy: 23 resources: 24 limits: 25 memory: 512M2627secrets: 28 db_password: 29 file: ./secrets/db_password.txt05Using the Right Environment
Run with the appropriate overrides:
[terminal]
1# Development2docker compose -f docker-compose.yml -f docker-compose.dev.yml up34# Production5docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d67# Shortcut: create a .env file with COMPOSE_FILE8# echo "COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml" > .env9# Now just: docker compose upSet the COMPOSE_FILE environment variable in your shell profile or .env file so you don't have to pass -f flags every time. Use a colon separator for multiple files.