Skip to Content
Split Dev and Prod With Compose Override Files
Intermediate
0 likes
Tutorials

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
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.