How do ARG and ENV differ in a Dockerfile?

6 minintermediatedockerfileargenvbuild-arguments

Quick Answer

ARG defines a variable that only exists during the image build — it's not available in the running container at all unless explicitly also set as an ENV. ENV defines a variable that's baked into the image and persists into every container started from it, available to the application at runtime. Use ARG for build-time configuration (a version number to install, a build-target flag) and ENV for anything the running application itself needs to read.

Detailed Answer

ARG — build-time only, not present in the running container

ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-slim

ARG BUILD_ENV=production
RUN if [ "$BUILD_ENV" = "development" ]; then npm install; else npm ci --omit=dev; fi
docker build --build-arg NODE_VERSION=18 --build-arg BUILD_ENV=development -t myapp .

ARG values are supplied at build time (via --build-arg, or a default in the Dockerfile itself), and are only accessible during the build. Once the image is built and a container starts from it, none of these ARG values are present in the running container's environment at all, unless you deliberately also expose them via ENV (see below).

ENV — persists into the running container

ENV NODE_ENV=production
ENV PORT=3000
docker run myapp env | grep NODE_ENV
# NODE_ENV=production

ENV values are baked into the image itself and are automatically present as environment variables in every container started from that image. This is what the running application actually reads via its normal environment-variable access (process.env.NODE_ENV in Node, os.environ in Python, etc.).

Bridging the two: using an ARG to set a default ENV value

ARG APP_VERSION=dev
ENV APP_VERSION=${APP_VERSION}
docker build --build-arg APP_VERSION=2.1.0 -t myapp .
docker run myapp env | grep APP_VERSION
# APP_VERSION=2.1.0

This pattern lets a build-time value (perhaps injected by CI, reflecting the actual git tag/commit being built) become available to the running application at runtime too. Without this explicit bridging, an ARG's value would be invisible to the container once it's actually running. This is the detail most likely to cause confusion.

Why ARG values should never hold secrets

# NEVER do this
ARG API_KEY
RUN curl -H "Authorization: Bearer $API_KEY" https://example.com/fetch-something

Even though ARG values aren't automatically present in the running container's environment, they are recorded in the image's build history and are visible to anyone who can inspect the image. docker history can reveal build-time ARG/command details, depending on how they were used. Passing secrets via ARG is a common, real security mistake, since the value can end up baked into a layer's metadata even though it's not "in the environment" in the way ENV values are. Genuine build-time secrets (a private package registry token needed only during npm install) should use Docker's dedicated build secrets mechanism instead (RUN --mount=type=secret, part of BuildKit). This mechanism is specifically designed to make a secret available only during one RUN instruction's execution, without persisting it in any layer or image metadata at all.

Related Resources