Skip to content

Chapter 6 of 8

Multi-Environment Compose and Configuration

One of Compose's most powerful features is the ability to split a configuration across multiple files and merge them. The conventional layout is a base docker-compose.yml containing the shared services, a docker-compose.override.yml that is automatically merged on top for local development, and a docker-compose.prod.yml you opt into with -f. To start the production stack you run docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d. When multiple files are given, later files override earlier ones, and mappings are merged deeply, so you can add a key to a service in the override without rewriting the whole service block. The same merging can be triggered without CLI flags by setting the COMPOSE_FILE environment variable to a colon-separated list, or a semicolon-separated list on Windows.

Profiles let you mark a service as opt-in, so it only starts when you explicitly include its profile. A common pattern is to add profiles: [dev] to services like Adminer, MailHog, or a debug container, keeping the production up clean. To start with profiles, pass --profile dev or set COMPOSE_PROFILES=dev,debug. A service can be in multiple profiles, and it starts if any matching profile is active. This is much cleaner than commenting and uncommenting service blocks. The profiles key also gives you a way to define a one-shot migration or seed service that runs on demand rather than on every up, by giving it a profile and combining it with the service_completed_successfully condition pattern.

Configuration precedence for environment variables follows a strict order that you need to internalize when debugging. From highest to lowest precedence: a -e flag on docker compose run, the service's environment: block, the service's env_file:, the shell environment, and finally ENV directives baked into the Dockerfile. For secrets, Compose's secrets: top-level key declares file-backed or external secrets that mount inside the container at /run/secrets/<name> as files, never as environment variables, which is the secure default. For local development, a gitignored .env file referenced via env_file is the most pragmatic option, but in production you should pull secrets from a real secrets manager or a CI-injected file. BuildKit also supports build-time secrets via build.secrets, used in the Dockerfile with --mount=type=secret, which never get cached in image layers. The project name, which prefixes container and volume names, can be changed with -p projname or the COMPOSE_PROJECT_NAME environment variable, and the working directory Compose uses can be overridden with --project-directory or COMPOSE_PROJECT_DIR.

All chapters
  1. 1Compose File Anatomy
  2. 2Services, Dependencies, and Health
  3. 3Volumes, Mounts, and Data Persistence
  4. 4Networking in Compose
  5. 5Building, Scaling, and Resources
  6. 6Multi-Environment Compose and Configuration
  7. 7Logs, Debugging, and Common Gotchas
  8. 8Stack-Specific Recipes and Patterns

Drill it

Reading is not remembering. These come from the Docker Compose Recipes For Local Dev deck:

Q

Compose file directive to mount a host directory into a container?

volumes: - ./local:/app

Q

Restart a container automatically unless explicitly stopped?

restart: unless-stopped

Q

Define an environment variable in Compose?

environment:&lt;br&gt; - KEY=value or env_file: .env

Q

Wait for another service to be 'healthy' before starting?

depends_on:&lt;br&gt; db:&lt;br&gt; condition: service_healthy