Per-Environment Files

Multiple Compose Files for Dev, Test and Production

-f merges files left to right, resolving relative paths from the first file's directory. BookNest keeps compose.yaml as the complete definition Jenkins and Kubernetes reuse, with small files on top: compose.dev.yaml for development, compose.prod.yaml for a server, and nothing extra for tests (the profile of Profiles for Optional Services). COMPOSE_FILE=compose.yaml:compose.dev.yaml in a developer's own .env makes plain docker compose commands use the development pair.

compose.prod.yaml: production settings over compose.yamlYAML
# Production: docker compose -f compose.yaml -f compose.prod.yaml up -d
services:
  api:
    environment:
      PGPASSWORD: !reset null
      PGPASSWORD_FILE: /run/secrets/db_password
    secrets: [db_password]
    restart: unless-stopped
    deploy: { resources: { limits: { cpus: "1.0", memory: 256M } } }
    develop: !reset {}
  db:
    ports: !reset []
    environment:
      POSTGRES_PASSWORD: !reset null
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets: [db_password]
    restart: unless-stopped
    deploy: { resources: { limits: { cpus: "1.0", memory: 512M } } }
secrets:
  db_password:
    file: ./secrets/db_password.txt
Renaming the override and checking the merged production fileShell
mv compose.override.yaml compose.dev.yaml
P="-f compose.yaml -f compose.prod.yaml"
docker compose $P config db | grep -cE 'published|POSTGRES_PASSWORD:'
docker compose $P config api | grep -E '^      PGPASS|restart: u|memory: "2'
Output
0
          memory: "268435456"
      PGPASSWORD_FILE: /run/secrets/db_password
    restart: unless-stopped
    restart: unless-stopped

ports: !reset [] removed the database's published port, since nothing outside the stack should reach PostgreSQL 1,289 on a server, and !reset null removed both plain-text passwords, replaced by a secret next. Keep such files small, or environments drift apart again.