Making the final merged configuration from multiple -f files visible
docker compose config shows the configuration actually merged from multiple -f files and checks it for syntax errors before the stack is even started, surfacing override problems far faster than debugging an already running container.
Table of Contents
- 1. The Problem of Multiple Compose Files and Overrides
- 2. The Basics of docker compose config
- 3. Understanding Merge Order and Override Rules
- 4. Syntax Validation Before the Start
- 5. A Debugging Example With a Broken Override File
- 6. How Environment Variables and .env Are Resolved in the Config
- 7. Filtering Profiles in the Config Output
- 8. Using It in CI for Upfront Validation
- 9. Practical Tips for Everyday Use
- 10. Summary
- 11. FAQ
1. The Problem of Multiple Compose Files and Overrides
As soon as a project outgrows a single docker-compose.yml and uses additional files for development, staging, and production, for example docker-compose.override.yml or docker-compose.prod.yml, the actually effective configuration quickly becomes hard to follow. Every additional file can overwrite, add to, or append values from previous files depending on the key and data type, which leads to surprises without precise knowledge of the merge rules.
A common symptom is that a service starts with a completely different image or an unexpected environment variable than specified in the main compose file, because one of the additional -f files silently overwrote the value. Without a tool that explicitly shows the merged configuration, tediously tracing values manually through all involved files is often the only option.
2. The Basics of docker compose config
docker compose config reads all compose files given via -f in the order passed, applies the merge rules, and prints the complete, resolved configuration as YAML to standard output, without actually starting any container. That makes the command the ideal first step for any troubleshooting of unexpected compose stack behavior.
In the output, all variables from .env files are already interpolated, all relative paths resolved into absolute paths, and all YAML anchors and aliases expanded, so the output exactly matches what Docker Compose would actually pass to the Docker engine when the stack is started.
# Show the merged configuration from multiple files
docker compose -f docker-compose.yml -f docker-compose.override.yml config
# Show the configuration for the production environment
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
3. Understanding Merge Order and Override Rules
The order of the -f flags is decisive: every additional file is layered on top of the previous ones, and later files win for simple scalar values like image or restart. For lists like ports or volumes, behavior depends on the specific key, some lists are fully replaced, while environment and labels are merged as key-value structures and can override individual keys without losing the remaining entries.
These different per-key merge strategies are the root of many override surprises: anyone expecting ports in an override file to add to the base file gets caught off guard by the actual replacement logic when suddenly only the port defined in the override file is active. docker compose config makes exactly this gap between expectation and actual behavior immediately visible.
4. Syntax Validation Before the Start
Beyond simply displaying the merged configuration, docker compose config automatically checks the syntax of all included files and reports errors such as malformed YAML, unknown top-level keys, or invalid values for known fields, before any container is started. This is especially valuable in CI pipelines, where a broken compose stack would otherwise only surface during the actual deploy attempt.
The --quiet option suppresses the full YAML output and returns only the exit code, which makes the command ideal for automated validation steps that just want to check whether a configuration is valid, without writing the potentially long output into logs or processing it further.
# Only check syntax, no output of the full configuration
docker compose -f docker-compose.yml -f docker-compose.override.yml config --quiet
echo "Exit code: $?"
# A syntax error produces a clear error message
docker compose -f docker-compose.yml -f docker-compose.broken.yml config
5. A Debugging Example With a Broken Override File
A typical scenario: a team adds a docker-compose.override.yml to use a different database port locally, but accidentally redefines the entire ports block instead of only changing that one port. The service starts fine, but a second port defined in the base file for a debug endpoint is suddenly unreachable, with no error message pointing to why.
docker compose config surfaces this behavior immediately: in the merged output, only the one port defined in the override file appears, the second one is missing entirely. This gap between what was supposedly intended as additive in the compose file and what actually gets merged is barely visible in the individual files by eye, but immediately obvious in the resolved output.
6. How Environment Variables and .env Are Resolved in the Config
Values in compose files that reference environment variables, for example ${DATABASE_URL} or ${TAG:-latest} with a default, appear fully resolved in the output of docker compose config, immediately showing which actual value is used instead of just the placeholder in the source. This is especially helpful when multiple .env files or exported shell variables compete for precedence.
If a referenced variable is missing entirely and no default is given, Docker Compose replaces the placeholder with an empty string and, in older versions, often only issues a warning, which in practice can lead to hard-to-trace empty configuration values. A look at the config output surfaces exactly these empty values before they are passed to a container as seemingly valid but actually wrong configuration.
# Check variable resolution in the config
DATABASE_URL=postgres://user:pass@db:5432/app \
docker compose -f docker-compose.yml config | grep -A2 environment
7. Filtering Profiles in the Config Output
Compose profiles let services be made optional, started only when needed, for example an additional debugging container that should not run in normal operation. docker compose config takes active profiles into account via the --profile flag and shows only the services that would actually be activated with the given profiles, which helps understand which services actually start in a given environment.
Without an explicitly given profile, docker compose config by default shows all services including profile-bound ones, but with an indication that they will not be started by the actual up command without an active profile. This distinction between 'present in the configuration' and 'actually started' is a common point of confusion that a targeted look with --profile clears up.
8. Using It in CI for Upfront Validation
In CI pipelines, an early docker compose config --quiet step right after checkout, before any build or deploy is attempted, pays off, because a broken compose setup is caught within seconds instead of only failing after a lengthy build step. This saves valuable pipeline time, especially for monorepos with multiple compose files.
Additionally, the full config output can be stored as an artifact in the pipeline, so that in a later deploy problem it is immediately traceable which configuration was actually merged and used at the time of that particular run, which can save considerable time during troubleshooting compared to trying to manually reconstruct the merge logic afterward.
# .gitlab-ci.yml snippet: compose validation as its own job
validate-compose:
stage: validate
script:
- docker compose -f docker-compose.yml -f docker-compose.prod.yml config --quiet
- docker compose -f docker-compose.yml -f docker-compose.prod.yml config > compose-resolved.yml
artifacts:
paths:
- compose-resolved.yml
9. Practical Tips for Everyday Use
A proven habit is running docker compose config not only when troubleshooting but routinely after every change to one of the involved compose files, ideally as a local git pre-commit hook or as an explicit step in the developer documentation. That way merge surprises get caught before the commit, rather than at the next deploy or by a colleague using the same compose files with a different -f order.
For teams with many environments, it is also worth storing a fixed set of -f flags per target environment in a Makefile or shell alias, so the order never gets accidentally swapped, since a swapped order results in a completely different merged configuration without Docker Compose itself warning about it.
| Flag/command | Purpose | Typical use | Note |
|---|---|---|---|
| docker compose config | Show merged configuration | Manual debugging of overrides | Shows fully resolved YAML |
| config --quiet | Validate syntax only | CI upfront check without a build | Returns exit code, no output |
| config --profile x | Profile-filtered view | Check what is active per profile | Shows only services of that profile |
| config > file.yml | Export resolved configuration | Artifact for later troubleshooting | Useful for deploy problems |
Mironsoft
Container infrastructure, CI pipelines and deployment automation
Docker setups that hold up across the team and in production?
We review existing Dockerfiles and Compose stacks for security gaps, bloated images and fragile build pipelines, then build a container infrastructure that builds fast, runs securely and stays understandable across the team.
Dockerfile Review
Systematically optimizing multi-stage builds, layer caching and image size.
Security Audit
Hardening container isolation, secrets handling and image scanning against real attack surfaces.
CI/CD Integration
Building build pipelines, registries and deployment strategies for reproducible releases.
10. Summary
docker compose config: The Essentials at a Glance
Purpose
Make the actually merged configuration from multiple -f files visible.
Merge rules
Scalars get replaced, environment/labels merge selectively, lists depend on the key.
Validation
--quiet checks syntax before every build or deploy, ideal for CI upfront checks.
Practice
Store a fixed -f order per environment in a Makefile/alias.