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.
ARGlives during the build of the image- While
ENVis 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:
- Build time: when Docker builds the image with
docker build. - Runtime: when Docker starts a container with
docker runordocker 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:
| Instruction | Moment | Stays in the image | Available in the container |
|---|---|---|---|
ARG | Build | No as a final variable | No, unless you copy it to ENV |
ENV | Build and runtime | Yes | Yes |
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
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)
This is useful for values that are part of the normal behavior of the image:
NODE_ENV=productionASPNETCORE_URLS=http://+:8080TZ=Europe/Madrid- 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"]
And we build it like this:
docker build \
--build-arg NODE_VERSION=20 \
--build-arg BUILD_MODE=production \
-t my-app .
Several things happen here:
NODE_VERSIONdecides which base image we use.BUILD_MODEexists during the build.NODE_ENVis saved in the image as an environment variable.PORTremains 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
And so is this:
docker build --build-arg API_TOKEN=supersecret .
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.
