docker.recipes
Fundamentals6 min read

Docker Compose Override Files: Base + Environment Pattern

Use override files to manage different configurations for development, staging, and production.

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:latest
5 environment:
6 - DEBUG=false
7 - LOG_LEVEL=warn
8 ports:
9 - "80:8080"
10 db:
11 image: postgres:16-alpine
12 environment:
13 - POSTGRES_PASSWORD=${DB_PASSWORD}
14
15---
16# docker-compose.override.yml (auto-loaded for local dev)
17services:
18 app:
19 build: . # Build locally instead of pulling
20 environment:
21 - DEBUG=true # Override: enable debug
22 - LOG_LEVEL=debug # Override: verbose logging
23 ports:
24 - "8080:8080" # Override: different port
25 volumes:
26 - ./src:/app/src # Add: live code reload
27 db:
28 ports:
29 - "5432:5432" # Add: expose DB for local tools

Git-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 structure
2my-app/
3├── docker-compose.yml # Base config (always used)
4├── docker-compose.override.yml # Local dev (auto-loaded)
5├── docker-compose.prod.yml # Production overrides
6├── docker-compose.test.yml # Testing configuration
7└── docker-compose.debug.yml # Debug tools
8
9# Local development (automatic)
10docker compose up -d
11# Loads: docker-compose.yml + docker-compose.override.yml
12
13# Production deployment
14docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
15
16# Running tests
17docker compose -f docker-compose.yml -f docker-compose.test.yml up -d
18
19# Production + debug tools
20docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.debug.yml up -d

04Complete 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 configuration
2services:
3 app:
4 image: myapp:${APP_VERSION:-latest}
5 restart: unless-stopped
6 environment:
7 - DATABASE_URL=postgres://db:5432/myapp
8 depends_on:
9 - db
10 networks:
11 - frontend
12 - backend
13 db:
14 image: postgres:16-alpine
15 restart: unless-stopped
16 volumes:
17 - db_data:/var/lib/postgresql/data
18 networks:
19 - backend
20networks:
21 frontend:
22 backend:
23 internal: true
24volumes:
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 file
2services:
3 app:
4 environment:
5 - FOO=base
6 - BAR=base
7 ports:
8 - "80:80"
9 labels:
10 app.version: "1.0"
11
12# Override file
13services:
14 app:
15 environment:
16 - FOO=override # Replaces FOO
17 - BAZ=new # Adds BAZ
18 ports:
19 - "443:443" # Adds to ports list
20 labels:
21 app.env: "prod" # Adds new label
22
23# Result after merge:
24services:
25 app:
26 environment:
27 - FOO=override # Overridden
28 - BAR=base # Kept from base
29 - BAZ=new # Added
30 ports:
31 - "80:80" # From base
32 - "443:443" # Added
33 labels:
34 app.version: "1.0" # From base
35 app.env: "prod" # Added

Environment 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 configuration
2docker compose config
3
4# See merged config for specific files
5docker compose -f docker-compose.yml -f docker-compose.prod.yml config
6
7# Validate without starting
8docker compose config --quiet && echo "Config OK"