skip to content

An image is built from a Dockerfile containing `ENV LOG_LEVEL=info`. What value does the process see when the container is started with `docker run -e LOG_LEVEL=debug`, and what happens if the Dockerfile also declares `ARG LOG_LEVEL` and the build is run with `--build-arg LOG_LEVEL=trace`?

level: middleimportance: must knowfreq 50%

answer

  1. -e > --env-file > image ENV
  2. override affects one container, image unchanged
  3. ENV beats same-named ARG during build
  4. bridge: ARG X=default then ENV X=${X}
  5. exec-form CMD does not expand $VARS

basics

~20 s

The container sees debug: docker run -e overrides the image's ENV default. During the build, an ENV always beats a same-named ARG, so --build-arg LOG_LEVEL=trace is ignored for later build steps unless the ENV is written as ENV LOG_LEVEL=${LOG_LEVEL}.

solid answer

~40 s

**At run time**, ENV in the image is only a default. Precedence, strongest first: `docker run -e` / `--env-file` (or Compose `environment:`/`env_file:`) → the image's `ENV` → nothing. So the process sees `debug`, and the image itself is unchanged — another container from the same image still gets `info`. **At build time**, Docker documents that when an ENV and an ARG share a name, **ENV wins** for the remainder of the build. With a bare `ARG LOG_LEVEL` plus `ENV LOG_LEVEL=info`, `--build-arg LOG_LEVEL=trace` has no effect on later `RUN` steps or on the image's environment. To make a build arg actually flow into the image you must wire it explicitly: ```dockerfile ARG LOG_LEVEL=info ENV LOG_LEVEL=${LOG_LEVEL} ``` Now `--build-arg` sets the baked default and `docker run -e` still overrides it per container.

code

dockerfile · 8 lines
dockerfile
FROM eclipse-temurin:21-jre

ARG LOG_LEVEL=info          # build-time input
ENV LOG_LEVEL=${LOG_LEVEL}  # baked default, overridable at run time
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75"

COPY app.jar /app.jar
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -Dlog.level=$LOG_LEVEL -jar /app.jar"]

go deeper

for a junior

Know that -e at run time beats the image's ENV default and that the image itself is not modified.

for a middle

Add the build-side rule that ENV beats a same-named ARG, and write the ARG X + ENV X=${X} bridge correctly.

for a senior

Argue for runtime configuration so one immutable artefact is promoted across environments, and know how Compose's layering sits in front of the image ENV.

for a principal

Define the org contract: which knobs are build-time (image contents) versus runtime (platform-injected), so build reproducibility and artefact promotion are both preserved.

## Runtime precedence The image's `Config.Env` list — everything set by `ENV`, plus whatever the base image inherited — is the **default environment** for containers created from that image. It is not authoritative; the runtime layers on top of it. Strongest to weakest for a plain `docker run`: 1. `-e NAME=value` on the command line. 2. `--env-file file` entries. 3. The image's `ENV` values (including those inherited from the base image). With Compose the same shape applies, with Compose's own layering in front: `docker compose run -e` → the service's `environment:` → its `env_file:` → the image `ENV`. Note also `-e NAME` with no `=value`, which passes the variable through from the invoking shell — handy, and a way to accidentally leak host environment into a container. Crucially, an override changes **that container only**. The image is immutable; `docker inspect` on the image still shows `LOG_LEVEL=info`, and a second `docker run` without `-e` gets `info` again. That immutability is the point: one image artefact promoted through dev/stage/prod, configured per environment at start. ## Build-time precedence between ARG and ENV Inside the build, both mechanisms put values into the environment of `RUN` commands, so they can collide. Docker's documented rule is that an `ENV` with the same name **takes precedence over an `ARG`** for the rest of the build. Concretely: ```dockerfile ARG LOG_LEVEL=info ENV LOG_LEVEL=warn RUN echo $LOG_LEVEL # prints warn, even with --build-arg LOG_LEVEL=trace ``` People hit this when they try to "parameterise" an image whose base already sets the variable with `ENV` — for example overriding `NODE_ENV` or a base image's `JAVA_HOME` by passing a build arg, which quietly does nothing. The fix is always to make the dependency explicit by assigning the ARG into the ENV. ## The intentional bridge ```dockerfile ARG LOG_LEVEL=info ENV LOG_LEVEL=${LOG_LEVEL} ``` Read it as: the build accepts an input, and the image records the chosen default. This gives three layers of control — the Dockerfile default, the build-time override, and the run-time override — which is usually exactly what a platform team wants. Ordering matters: the `ARG` must appear above the `ENV` that references it, and both must be inside the stage that produces the final image (an `ENV` set in an earlier multi-stage stage does not survive into the final one). ## Where a value ends up mattering - **`ENTRYPOINT`/`CMD` in exec form do not run a shell**, so `CMD ["sh", "-c", "echo $LOG_LEVEL"]` expands the variable but `CMD ["echo", "$LOG_LEVEL"]` prints the literal text. Environment overrides are still delivered to the process; it is the *expansion in the instruction* that needs a shell. - **Some tools read only their own config**, so setting an env var that the application does not consult achieves nothing — check the application's precedence rules too (many frameworks put command-line flags above environment above config files). - **PATH-like variables**: `ENV PATH=/opt/tool/bin:$PATH` is a common append idiom; overriding `PATH` at run time with `-e` replaces it wholesale and can break the entrypoint. ## Practical guidance - Prefer **runtime** configuration (ENV overridden at start) over **build-time** configuration for anything that differs by environment. Baking `LOG_LEVEL=debug` at build time forces a rebuild per environment and defeats artefact promotion. - Use build args for things that genuinely change the *contents* of the image: dependency versions, base tags, target architecture, feature-flagged compilation. - Give every runtime variable a sensible `ENV` default in the Dockerfile so the image runs with no flags at all; treat the override as the exception. - Verify rather than assume: `docker inspect -f '{{json .Config.Env}}' image` shows the baked defaults, and `docker exec <container> env` shows what the running container actually has. ## What interviewers are listening for Two sentences: "run-time `-e` beats image `ENV`, and the image is unchanged", and "ENV beats a same-named ARG during the build, so you must assign the ARG into the ENV explicitly". Candidates who know only the first half typically write Dockerfiles that silently ignore their own `--build-arg` flags.

  • A base image sets `ENV NODE_ENV=production`. A downstream Dockerfile declares `ARG NODE_ENV` and builds with `--build-arg NODE_ENV=development`. What do the RUN steps see?
    They see `production`. An ENV inherited from the base image is already part of the build environment, and a same-named ARG does not take precedence over it. The downstream Dockerfile must explicitly write `ENV NODE_ENV=${NODE_ENV}` after declaring the ARG to make the build argument effective.
  • Why is baking environment-specific values at build time usually a bad idea?
    It ties the artefact to one environment, so promoting the same tested image from staging to production becomes impossible and every environment needs its own build. Runtime configuration keeps a single immutable image and moves the variation to the platform — env vars, secret injection, config mounts — which also means a config change does not require rebuilding and re-scanning an image.
  • Does `docker run -e` change the image?
    No. Images are immutable; `-e` only populates the container's environment at creation time and is recorded in the container's config, not the image's. `docker inspect` on the image still reports the original ENV values, and any other container started without the flag gets the baked default.

saying these in an interview costs you the question

  • Believing `--build-arg` overrides an ENV of the same name during the build.
  • Thinking `docker run -e` mutates the image so later containers inherit the value.
  • Baking per-environment values at build time and rebuilding the image per environment.
  • Expecting `CMD ["echo", "$VAR"]` in exec form to expand the variable without a shell.
  • Overriding `PATH` at run time with `-e PATH=...` and wondering why the entrypoint stops working.

context