Skip to main content

Multi-Environment Overrides

Most applications need different settings for development, staging, and production -- different ports, volumes, resource limits, and image tags. Compose handles this with override files that layer on top of a base configuration.

How It Works​

Compose automatically merges compose.override.yaml with the base file. For other environments, you specify files explicitly.

File Layout​

project/
├── compose.yaml # Base: services, networks, volumes
├── compose.override.yaml # Auto-loaded for local dev
├── compose.prod.yaml # Production overrides
├── .env.example # Template for env vars
└── .env # Local env vars (gitignored)

What Goes Where​

SettingBase FileDev OverrideProduction Override
Service definitions✓
Network topology✓
Named volumes✓
Source code bind mounts✓
Debug ports (127.0.0.1)✓
Pinned image tags✓
Resource limits✓
Strict restart policies✓
Public port bindings✓

Example: Base File​

# compose.yaml -- shared architecture
services:
api:
image: registry.example.com/api:${API_TAG:-latest}
networks: [back]
depends_on:
db: { condition: service_healthy }

db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
networks: [back]

networks:
back: {}

volumes:
pgdata: {}

Example: Dev Override​

# compose.override.yaml -- auto-loaded for local development
services:
api:
build: ./api # Build from source instead of pulling image
volumes:
- ./api/src:/app/src # Live code reload
ports:
- "127.0.0.1:3000:3000" # Local debug access
environment:
LOG_LEVEL: debug

Example: Production Override​

# compose.prod.yaml -- explicit production hardening
services:
api:
image: registry.example.com/api:1.5.2 # Pinned tag
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"

db:
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G

Deploying Each Environment​

# Local development (auto-merges compose.override.yaml)
docker compose up -d

# Production (explicit file selection)
docker compose -f compose.yaml -f compose.prod.yaml up -d

# Staging
docker compose -f compose.yaml -f compose.staging.yaml up -d

Always Validate Before Deploy​

Render the merged configuration and review it:

# See exactly what will be deployed in production
docker compose -f compose.yaml -f compose.prod.yaml config

This catches merge issues, missing variables, and unexpected overrides before they cause problems.

Key Takeaways​

  • Keep the base file environment-agnostic with shared service definitions, networks, and volumes.
  • Use compose.override.yaml for local dev conveniences (bind mounts, debug ports, build from source). It is merged automatically.
  • Use compose.prod.yaml for production hardening (pinned tags, resource limits, restart policies). Specify explicitly with -f.
  • Always run docker compose config with your file flags to see the merged result before deploying.
  • Do not duplicate entire service definitions across override files -- only include the keys you are changing.

What's Next​