dockerfile-arg-env

ARG and ENV in Dockerfile

  • 5 min

ARG and ENV in Dockerfile are two different ways to define variables within the build process.

They seem almost the same, because both use names and values. But they are not.

  • ARG lives during the build of the image
  • While ENV is saved in the final image and will be available when the container starts.

This difference is important. Because if we mix the concepts, we end up with weird Dockerfiles, images that are difficult to configure, or worse still, secrets and passwords baked into the image.

Build variables and runtime variables

The first thing is to separate two different moments:

  1. Build time: when Docker builds the image with docker build.
  2. Runtime: when Docker starts a container with docker run or docker compose up.

ARG primarily belongs to the first moment. It is used to parameterize the build.

ENV belongs to the second moment. It is used to define variables available inside the container.

We can summarize it like this:

InstructionMomentStays in the imageAvailable in the container
ARGBuildNo as a final variableNo, unless you copy it to ENV
ENVBuild and runtimeYesYes

Let’s look at it slowly 👇.

ENV

The ENV instruction defines an environment variable inside the image. For example:

ENV NODE_ENV=production
ENV APP_PORT=3000
Copied!

When a container starts from that image, it will have these variables available.

In a Node.js application, for example, we could read:

console.log(process.env.NODE_ENV)
console.log(process.env.APP_PORT)
Copied!

This is useful for values that are part of the normal behavior of the image:

  1. NODE_ENV=production
  2. ASPNETCORE_URLS=http://+:8080
  3. TZ=Europe/Madrid
  4. paths like PATH

That is, things that make sense as default values for the image.

Complete example

Let’s put both things together in a simple Dockerfile:

ARG NODE_VERSION=22

FROM node:${NODE_VERSION}-alpine

WORKDIR /app

ARG BUILD_MODE=production
ENV NODE_ENV=${BUILD_MODE}
ENV PORT=3000

COPY package*.json ./
RUN npm ci

COPY . .

EXPOSE 3000
CMD ["npm", "start"]
Copied!

And we build it like this:

docker build \
  --build-arg NODE_VERSION=20 \
  --build-arg BUILD_MODE=production \
  -t my-app .
Copied!

Several things happen here:

  1. NODE_VERSION decides which base image we use.
  2. BUILD_MODE exists during the build.
  3. NODE_ENV is saved in the image as an environment variable.
  4. PORT remains as the default value for the container.

This is a fairly common pattern.

Do not use ARG or ENV for secrets

Neither ARG nor ENV are a good place for passwords, tokens, or private keys.

With ENV, the problem is obvious: the value remains inside the image and can be seen with tools like docker inspect.

With ARG, the situation is a bit more treacherous. Although it does not remain as a final container variable, it can appear in the image history or build metadata.

Therefore, this is wrong:

ARG API_TOKEN=supersecret
ENV DB_PASSWORD=anotherkey
Copied!

And so is this:

docker build --build-arg API_TOKEN=supersecret .
Copied!

If you need secrets during the build, look into mechanisms like BuildKit secrets. If you need secrets when starting the container, pass them from the environment, Docker Compose, a secret manager, or the platform where you deploy.