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`?
answer
- -e > --env-file > image ENV
- override affects one container, image unchanged
- ENV beats same-named ARG during build
- bridge: ARG X=default then ENV X=${X}
- exec-form CMD does not expand $VARS
basics
~20 sThe 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 linesFROM 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
Know that -e at run time beats the image's ENV default and that the image itself is not modified.
Add the build-side rule that ENV beats a same-named ARG, and write the ARG X + ENV X=${X} bridge correctly.
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.
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.