Split Dev and Prod With Compose Override Files
Keep one base compose.yaml and merge a development override and a production file on top of it. After this, you can see what each environment gets with docker compose config before you run it.
Development and production need different settings: a published port and a mounted source folder on your laptop, restart policies and fixed image tags on a server. Copying the whole Compose file for each is how they drift apart. Compose can merge several files instead. This page uses a base file and two small overrides.
The base file
The base holds what never changes. It has no ports and no mounts.
services:
web:
image: nginx:1.30-alpine
The development override
A file named compose.override.yaml next to compose.yaml is merged in automatically, with no flag.
services:
web:
ports:
- "127.0.0.1:8096:80"
volumes:
- ./site:/usr/share/nginx/html:ro
environment:
MODE: dev
dev page from your folder
Set-Location envsplit-stack
docker compose up -d 2>&1 | Out-Null
Start-Sleep -Seconds 2
curl.exe -s http://localhost:8096
docker compose down 2>&1 | Out-Null
dev page from your folder
Plain docker compose up read both files. The page comes from your site folder, which only the override mounts.
The production override
Production gets its own file. It is never picked up by accident, because only compose.override.yaml is automatic.
services:
web:
restart: unless-stopped
environment:
MODE: prod
Name both files with -f, base first. Later files win where they set the same key. Check the result with config before you run it.
docker compose -f compose.yaml -f compose.prod.yaml config
name: envsplit-stack
services:
web:
environment:
MODE: prod
image: nginx:1.30-alpine
networks:
default: null
restart: unless-stopped
networks:
default:
name: envsplit-stack_default
There are no ports and no mounts, which is right for a service that sits behind a proxy. There is a restart policy, and MODE is prod. Without -f, the same config command shows the development result. Compare them, and you see exactly what each environment gets.
docker compose config --no-path-resolution
name: envsplit-stack
services:
web:
environment:
MODE: dev
image: nginx:1.30-alpine
networks:
default: null
ports:
- mode: ingress
host_ip: 127.0.0.1
target: 80
published: "8096"
protocol: tcp
volumes:
- type: bind
source: ./site
target: /usr/share/nginx/html
read_only: true
bind: {}
networks:
default:
name: envsplit-stack_default
Lists such as ports and volumes are merged, so an override adds to them. A single value, such as image or one environment variable, is replaced by the later file.
💡 Tip: You can set the file list once, in the COMPOSE_FILE variable. On Windows the separator is a semicolon, and on Linux it is a colon: $env:COMPOSE_FILE = "compose.yaml;compose.prod.yaml". A colon on Windows breaks the drive letters in paths.
âš Warning: Keep secrets out of all three files. They go in .env files and secrets that are not committed, as the lesson on environment files explains.
Related course: Docker Intermediate.
About
Keep one base compose.yaml and merge a development override and a production file on top of it. After this, you can see what each environment gets with docker compose config before you run it.
- Responsible
- Jay P
- Last Update
- 08/11/2026
- Reading Time
- 3 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.