01The Override Mechanism
Docker Compose automatically merges docker-compose.yml with docker-compose.override.yml if it exists. This lets you keep a base configuration and override specific values for local development without touching the main file.
02Automatic Override Loading
When you run docker compose up, it automatically loads both files and merges them. Values in the override file take precedence.
1# docker-compose.yml (base configuration)2services: 3 app: 4 image: myapp:latest5 environment: 6 - DEBUG=false7 - LOG_LEVEL=warn8 ports: 9 - "80:8080"10 db: 11 image: postgres:16-alpine12 environment: 13 - POSTGRES_PASSWORD=${DB_PASSWORD}1415---16# docker-compose.override.yml (auto-loaded for local dev)17services: 18 app: 19 build: . # Build locally instead of pulling20 environment: 21 - DEBUG=true # Override: enable debug22 - LOG_LEVEL=debug # Override: verbose logging23 ports: 24 - "8080:8080" # Override: different port25 volumes: 26 - ./src:/app/src # Add: live code reload27 db: 28 ports: 29 - "5432:5432" # Add: expose DB for local toolsGit-ignore docker-compose.override.yml so each developer can have their own local settings.
03Using Multiple Override Files
For more complex setups, use the -f flag to compose multiple files. They're merged in order—later files override earlier ones.
1# File structure2my-app/3├── docker-compose.yml # Base config (always used)4├── docker-compose.override.yml # Local dev (auto-loaded)5├── docker-compose.prod.yml # Production overrides6├── docker-compose.test.yml # Testing configuration7└── docker-compose.debug.yml # Debug tools89# Local development (automatic)10docker compose up -d11# Loads: docker-compose.yml + docker-compose.override.yml1213# Production deployment14docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d1516# Running tests17docker compose -f docker-compose.yml -f docker-compose.test.yml up -d1819# Production + debug tools20docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.debug.yml up -d04Complete Multi-Environment Example
Here's a real-world setup with base, development, and production configurations. The base file contains shared configuration, override adds dev tools, and prod adds production optimizations.
1# docker-compose.yml - Base configuration2services: 3 app: 4 image: myapp:${APP_VERSION:-latest}5 restart: unless-stopped6 environment: 7 - DATABASE_URL=postgres://db:5432/myapp8 depends_on: 9 - db10 networks: 11 - frontend12 - backend13 db: 14 image: postgres:16-alpine15 restart: unless-stopped16 volumes: 17 - db_data:/var/lib/postgresql/data18 networks: 19 - backend20networks: 21 frontend: 22 backend: 23 internal: true24volumes: 25 db_data: Use docker-compose.override.yml for local dev (auto-loaded), docker-compose.prod.yml for production (use -f flag).
05Understanding Merge Behavior
Knowing how values merge helps avoid surprises:
**Scalar values** (strings, numbers): Later file wins
**Lists** (ports, volumes): Combined/appended
**Maps** (environment, labels): Merged by key
To completely replace a list instead of merging, you may need to restructure.
1# Base file2services: 3 app: 4 environment: 5 - FOO=base6 - BAR=base7 ports: 8 - "80:80"9 labels: 10 app.version: "1.0"1112# Override file13services: 14 app: 15 environment: 16 - FOO=override # Replaces FOO17 - BAZ=new # Adds BAZ18 ports: 19 - "443:443" # Adds to ports list20 labels: 21 app.env: "prod" # Adds new label2223# Result after merge:24services: 25 app: 26 environment: 27 - FOO=override # Overridden28 - BAR=base # Kept from base29 - BAZ=new # Added30 ports: 31 - "80:80" # From base32 - "443:443" # Added33 labels: 34 app.version: "1.0" # From base35 app.env: "prod" # AddedEnvironment variables in list format (- KEY=value) merge differently than map format (KEY: value). Stick to one format consistently.
06Override File Best Practices
**1. Git-ignore local overrides**
Add docker-compose.override.yml to .gitignore. Provide a template instead.
**2. Document environment-specific files**
Name files clearly: docker-compose.prod.yml, not docker-compose.2.yml
**3. Keep base file minimal**
Put only shared configuration in the base. Environment-specific settings go in overrides.
**4. Use .env for secrets**
Don't put passwords in compose files. Use environment variables from .env.
**5. Validate merged config**
Check the final result with docker compose config.
1# See the merged configuration2docker compose config34# See merged config for specific files5docker compose -f docker-compose.yml -f docker-compose.prod.yml config67# Validate without starting8docker compose config --quiet && echo "Config OK"