skip to content

With Docker Compose, if LOG_LEVEL is set by `docker compose run -e`, the service's `environment`, its `env_file` and the image's `ENV`, which value does the container process see?

level: seniorimportance: should knowfreq 31%

answer

  1. command line beats file
  2. inline beats file-based
  3. the image is the fallback
  4. interpolation resolves before the ranking

basics

~20 s

docker compose run -e wins, then the service's environment, then env_file, and the image's ENV only applies when Compose sets nothing for that name. Any ${VAR} inside environment or env_file is interpolated first, from the shell or env files.

solid answer

~40 s

For the container, Compose ranks sources as: `docker compose run -e` (or `--env`) first; then the service's `environment`; then `env_file`; and last the `ENV` baked into the image, which shows through only when no Compose source sets that name. Interpolation happens before this ranking, so `environment: LOG_LEVEL: ${LOG_LEVEL:-info}` is really "whatever the shell or env file says, else info" — that is why the docs list interpolated values above plain ones. Bare keys pass values through: `environment: [LOG_LEVEL]` or `run -e LOG_LEVEL` take the value from the shell, else from the loaded env files. `docker compose up` has no `-e` flag, and a container keeps the environment it was created with: an edited `environment` needs `docker compose up -d`, which recreates changed containers, not `restart`.

code

yaml · 8 lines
yaml
services:
  api:
    image: registry.example/api:1.4   # image sets ENV LOG_LEVEL=warn
    env_file:
      - ./api.env                     # LOG_LEVEL=info
    environment:
      LOG_LEVEL: ${LOG_LEVEL:-debug}
      DB_PASSWORD:                    # pass-through, no value

go deeper

for a junior

Recall the simple ranking: run -e beats environment, environment beats env_file, and the image's ENV is only the fallback.

for a middle

Explain why interpolation sits outside that ranking: ${VAR} inside environment is resolved from the shell or env files first, and bare keys pass values through.

for a senior

Show you can trace a wrong value in a running container: check run flags, environment, env_file, then the image, and know that restart keeps the old environment while up -d recreates.

for a principal

Weigh where configuration should live across a team's stacks — image defaults, env_file per environment, environment for overrides — so that one layer owns each setting and surprises stay rare.

## Where a container's variables come from A container started by Docker Compose gets its environment from up to four places. Three belong to Compose and one to the image: - **`docker compose run -e KEY=value`** (long form `--env`) — only for one-off containers started with `run`. - **`environment:`** — inline pairs on the service in `compose.yaml`. - **`env_file:`** — files whose `KEY=value` lines are loaded for the service. - **Image `ENV`** — defaults written into the image when it was built. Variables in the shell and in the project `.env` are **not** on this list by themselves. They only matter when a Compose source refers to them. ## The ranking, highest first The Docker documentation orders the sources like this: 1. `docker compose run -e`. 2. An `environment` or `env_file` value that was **interpolated** from the shell or an env file, such as `LOG_LEVEL: ${LOG_LEVEL}`. 3. A plain value in `environment`. 4. A value from `env_file`. 5. The image's `ENV`, which applies only when none of the above sets the name. Item 2 is not a separate attribute: it is `environment` or `env_file` whose value came from interpolation, the point covered next. In day-to-day terms the rule is **`run -e` > `environment` > `env_file` > image `ENV`**. ## Interpolation runs before the ranking Interpolation replaces `${VAR}` while Compose parses the file, long before a container exists. So: - `environment: LOG_LEVEL: ${LOG_LEVEL:-info}` means the shell's value if exported, else the env file's value, else `info` — and that result then outranks `env_file` and the image. - A value inside an `env_file` can use `${VAR}` too; it is resolved from the same interpolation environment. - "The shell beats `environment`" is therefore only true when `environment` asks for the shell's value. ## Worked outcomes | `run -e` | `environment` | `env_file` | image `ENV` | Container sees | |---|---|---|---|---| | — | — | `LOG_LEVEL=info` | `LOG_LEVEL=warn` | `info` | | — | `LOG_LEVEL: debug` | `LOG_LEVEL=info` | `LOG_LEVEL=warn` | `debug` | | `LOG_LEVEL=trace` | `LOG_LEVEL: debug` | `LOG_LEVEL=info` | `LOG_LEVEL=warn` | `trace` | | — | — | — | `LOG_LEVEL=warn` | `warn` | | `LOG_LEVEL` (no value), shell has `error` | `LOG_LEVEL: debug` | — | `LOG_LEVEL=warn` | `error` | ## Pass-through forms A key without a value asks Compose to fill it in: - `environment: [LOG_LEVEL]` takes the value from Compose's environment — the shell first, then the loaded env files. If neither has it, Compose sets nothing for that name and prints **no warning**. - `environment: [LOG_LEVEL=${LOG_LEVEL}]` behaves similarly but **warns** when the variable is unset and sets it to an empty string. - `docker compose run -e LOG_LEVEL web` copies the value in the same way for a one-off container. ## Debugging a wrong value When a running service shows an unexpected value, walk the ranking from the top: 1. **Was it a one-off?** A container started with `docker compose run -e` has its own override; the long-running service container does not. 2. **What does the container actually have?** `docker compose exec web env` prints the environment of the running service container. 3. **What does Compose intend to send?** `docker compose config web` renders the service with `env_file` folded into `environment`, so a value set by either shows up there. 4. **Where did an interpolated value come from?** If `environment` uses `${VAR}`, the answer is in the shell or the env file, not in `compose.yaml`. 5. **Is it the image?** If no Compose source sets the name, the value is the image's `ENV` default. ## When changes take effect The environment is fixed when a container is **created**: 1. `docker compose up -d` compares each service's configuration with its running container and recreates the ones that changed, so an edited `environment` lands. 2. `docker compose restart` restarts the existing container with its old configuration; its reference page says changes to environment variables are not reflected. 3. `docker compose exec -e KEY=value web cmd` sets the variable for that one command only, not for the service's main process. 4. `docker compose up` has no `-e` flag; a one-off override for the stack goes through an interpolated `environment` entry and an exported shell variable. In the laptop-to-CI move, this is how a migration job gets its own settings: `docker compose run --rm -e DB_PASSWORD migrate` copies the CI job's secret into one container without touching `compose.yaml`.

  • docker compose run -e LOG_LEVEL web gives no value after LOG_LEVEL. Where does the value come from?
    From Compose's own environment: the shell running the command first, then the env file Compose loaded for interpolation (`.env` or `--env-file`). If neither defines it, the variable is not set by `-e` and the lower-ranked sources apply.
  • Why does docker compose restart not pick up an edited environment value?
    `restart` restarts the existing container, and a container's environment is fixed at creation. `docker compose up -d` notices that the service's configuration changed and recreates the container, which applies the new value.

saying these in an interview costs you the question

  • env_file overrides environment because the files are loaded afterwards.
  • The image's ENV always wins because it is baked into the image.
  • Shell variables are copied into every container without being listed.
  • docker compose restart applies an edited environment block.
  • docker compose up -e sets one variable for the whole stack.