How the merge works
Each file is parsed and interpolated before it joins the model. Mapping keys combine; later scalar values override earlier ones. Lists append distinct entries, with special handling for environment/label/build-argument names, service networks/dependencies, and unique ports, volumes, secrets, and configs. Commands, entrypoints, and healthcheck test lists replace earlier values.
Ports are identified by host IP, target, published port, and protocol. Volume, secret, and config declarations are identified by target. A changed published port therefore adds a binding unless the earlier list is cleared. Use !reset to remove a field or !override to replace it. The replacement tag requires Compose 2.24.4 or later. Supported YAML anchors and merge keys expand within one file; they never cross files.
Interpolation and precedence
Supported expressions are $VAR, ${VAR}, defaults ${VAR:-default} and ${VAR-default}, required values ${VAR:?message} and ${VAR?message}, and alternatives ${VAR:+value} and ${VAR+value}. Nested operands are supported. A colon tests whether a variable is both set and nonempty; without it, an empty supplied value still counts as set. $$ produces a literal dollar sign. Variable values are substituted once.
YAML values interpolate; mapping keys do not. NAME=value lists allow an interpolated environment or label name. The .env parser supports comments, quoted values, double-quote escapes, literal single quotes, and multiline single-quoted values. The shell snapshot box is literal assignment data. Automatic .env discovery, COMPOSE_FILE/COMPOSE_PROFILES self-configuration, multiple environment-file discovery, shell expansion, and container env_file loading are outside this model.
Masked reports and comparison
Container environment values, build arguments, labels, command/entrypoint arguments, and healthcheck tests are masked. Sensitive key and variable names, credential-bearing URLs, private keys, and values derived from sensitive variables are also masked throughout the model and port evidence. Variable inventories never include supplied values. JSON, CSV, YAML, copy, and print use the same masked report.
Masking follows field context and name/content detection; arbitrary credentials stored in an unrelated field may not be recognized. Source text remains visible in your input boxes. Changes are computed before masking, so two different secret values still produce a change row. Downloaded YAML is an audit artifact with placeholders, rather than a runnable Compose configuration.
Supported scope
This is a bounded declared-model audit. It handles core multi-file merge rules, common short/long forms, supplied interpolation variables, profile selection, dependencies, binding overlap, and source tracing. Unsupported includes, extends, service env_file/label_file contents, models, devices, build.extra_hosts, and network IPAM config merges make the report incomplete. Windows drive-letter volume syntax, short volume options beyond ro/rw, unsupported tags, malformed expressions, and incompatible override types stop the audit.
It does not run Docker, build images, read your actual shell, fetch remote files, resolve filesystem paths, load referenced secrets/configs, inject generated defaults or resource names, validate the entire Compose schema, inspect deployed stacks, or test host-port availability. Active profiles are explicit inputs; a service dependency does not automatically enable an inactive profile. Resource limits are 256 services, 2,000 active bindings, 40,000 expanded YAML nodes, 48 YAML levels, 20,000 report rows per section, and 250,000 work steps. Exceeding a limit aborts the audit.
Primary references
Docker documents multi-file merging and merge tags, Compose interpolation, environment-file syntax and precedence, and profile selection and dependencies. compose-go merge rules provide additional behavior for supported special fields. Check the final source configuration with your installed Compose version.
Frequently asked questions
Why did an override add a port instead of replacing it?
Port uniqueness includes both the published and target ports. Changing only the published port creates a new declaration. Clear or replace the ports list explicitly when you intend to remove the old binding.
Why is my service missing from the model?
A service with profiles is active only when a supplied profile matches, or when * is selected. Services without profiles are always active. Missing/inactive required dependencies are reported.
Does the .env box set every container variable?
No. It supplies interpolation values. Service environment declarations must reference or inherit those names. Service env_file content is a separate input that this tool does not load.
Why are both sides of a changed value masked?
The audit compares resolved values internally before masking the report. A changed private value remains a change even though its contents are omitted.