skip to content

How do you use `docker compose config` to trace where a service's wrong variable value came from, and what do `--environment`, `--variables` and `--no-interpolate` add?

level: middleimportance: should knowfreq 42%

answer

  1. render before you run
  2. raw placeholders versus resolved values
  3. what Compose itself saw
  4. a table of names and defaults

basics

~20 s

docker compose config prints the merged, interpolated model, with env_file contents folded into each service's environment. --no-interpolate shows which placeholder feeds a value, --environment lists the variables Compose resolved, and --variables lists every placeholder with its default.

solid answer

~50 s

`docker compose config` loads the project exactly as `up` would — merging the compose files, interpolating `${VAR}`, and by default folding each service's `env_file` into its `environment` — and prints the result without touching the engine. To trace a wrong value I work in three passes: `--no-interpolate` shows the raw text, so I learn which placeholder, if any, sets the attribute; `--environment` prints the interpolation environment Compose built from the shell and env files, so I see what that placeholder resolved to; and `--variables` lists each placeholder with whether it is required and its default. Running the same commands on the laptop and in the CI job pinpoints the source that differs. `-q` validates without printing, which also catches a missing `${VAR:?}`. The rendered output contains real secret values, so it does not belong in shared logs.

code

bash · 10 lines
bash
$ docker compose config --no-interpolate api | grep DB_PORT
      DB_PORT: ${DB_PORT:-5432}
$ docker compose config api | grep DB_PORT
      DB_PORT: "5433"
$ docker compose config --environment | grep DB_PORT
DB_PORT=5433
$ docker compose config --variables
NAME          REQUIRED   DEFAULT VALUE   ALTERNATE VALUE
DB_PASSWORD   true
DB_PORT       false      5432

go deeper

for a junior

Recall that docker compose config shows the compose file after variables are filled in, and that it is the first thing to run when a value looks wrong.

for a middle

Explain what the render includes — merged files, interpolated values, env_file folded into environment — and what --no-interpolate, --environment and --variables each reveal.

for a senior

Show a disciplined trace from a wrong container value back to its source, use config -q as an early pipeline gate, and keep rendered output with secrets out of shared logs.

for a principal

Treat the rendered model as the reviewable artifact of a stack: decide where it is validated, whether it is diffed between environments, and who may see it given the secrets it carries.

## What `docker compose config` renders `docker compose config` is Docker Compose's **renderer**: it builds the project model the way `docker compose up` would and prints it, without creating a container. By default the output is: - **Merged** — every file given with `-f` (or found by default) combined into one model. - **Interpolated** — every `${VAR}` placeholder replaced with its resolved value. - **Normalized** — short syntax expanded, so `ports: ["8080:80"]` appears in its long form. - **Path-resolved** — relative paths turned into absolute ones. - **Environment-resolved** — each service's `env_file` contents loaded and folded into its `environment`, with the `env_file` entry itself removed. Because it is the same loading code as `up`, a value you see in `config` is the value the container will be created with, apart from `docker compose run -e` overrides and the image's own `ENV`. ## A trace in four steps Suppose the API container in CI connects to port 5433 instead of 5432: 1. **Render the service**: `docker compose config api` shows `DB_PORT: "5433"` under `environment`, so the problem is in Compose's input, not in the application. 2. **Find the placeholder**: `docker compose config --no-interpolate api` shows `DB_PORT: ${DB_PORT:-5432}`, so the value comes from interpolation rather than a literal or an env file. 3. **Find the source**: `docker compose config --environment | grep DB_PORT` prints `DB_PORT=5433`, the value Compose resolved from the shell and env files together. 4. **Separate shell from file**: compare with `env | grep DB_PORT` and the env file in use. If the shell has it, the job's environment wins; otherwise an env file supplied it. ## The flags that matter for variables | Flag | What it prints | Use it to | |---|---|---| | (none) | the full interpolated model | see what a container will receive | | `--no-interpolate` | the model with `${VAR}` text left as written | find which placeholder feeds an attribute | | `--environment` | the interpolation environment, one `KEY=value` per line | see what each variable resolved to | | `--variables` | a table: `NAME`, `REQUIRED`, `DEFAULT VALUE`, `ALTERNATE VALUE` | list every variable the file expects | | `--no-env-resolution` | services with `env_file` references kept, not loaded | see which files a service names | | `-q`, `--quiet` | nothing; only the exit status | validate the model in a pipeline | | `--format json`, `-o FILE` | the model as JSON, or into a file | feed the rendered model to other tools | ## Reading the `--variables` table - **`REQUIRED`** is `true` for placeholders written with a `?` form, such as `${DB_PASSWORD:?}`. - **`DEFAULT VALUE`** is the fallback in a `-` form, so `${API_PORT:-8080}` shows `8080`. - **`ALTERNATE VALUE`** is the replacement in a `+` form. - It is computed from the whole model without interpolating, and it ignores any service names you pass. ## Limits of the render - **The image is invisible.** `config` knows nothing about variables defined by `ENV` inside the image; those appear only in the running container. - **One-off overrides are invisible.** Values given later with `docker compose run -e` are not part of the model. - **The environment is the current one.** `config` renders with the shell and env files of the moment you run it; an earlier `up` from a different shell may have created containers with other values. - **Profiles and files matter.** The render reflects the `-f` files and active profiles of that invocation, so compare like with like. ## Using it in CI - **Fail early.** A `docker compose config -q` step exits non-zero on an invalid file or a missing `${VAR:?}` before anything is pulled or started. - **Diff environments.** Running `docker compose config --environment` on a laptop and in the job exposes shell variables or env files that differ. - **Guard secrets.** The rendered model contains resolved values, including passwords interpolated into `environment` and everything folded in from `env_file`. Print it only where the audience may see those values, or redirect it with `-o` to a file you do not publish. - **Know its scope.** It shows what Compose will send; a process that later changes its own environment inside the container is outside its view.

  • What does docker compose config -q give a CI pipeline?
    A cheap validation step: it loads, merges and interpolates the project and prints nothing, but exits non-zero on an invalid file or a required `${VAR:?}` that is missing, before any image is pulled or container started.
  • Why is printing docker compose config output in a CI log risky?
    The output is the fully resolved model. Any password interpolated into `environment`, and every key folded in from `env_file`, appears in clear text, so a shared build log would expose it. Validate with `-q`, or write the model to a file with `-o` and keep that file private.

saying these in an interview costs you the question

  • docker compose config just prints compose.yaml exactly as it was written.
  • config leaves env_file references unresolved in its default output.
  • The --environment flag prints the environment of the running container.
  • docker compose config only works while the stack is up.
  • It is safe to print the rendered config in any CI log.