If you’ve worked with Docker for any length of time, you’re likely accustomed to writing or at least modifying a Dockerfile. This file can be thought of as a recipe for a Docker image; it contains both the ingredients (base images, packages, files) and the instructions (various RUN
, COPY
, and other commands that help build the image).
In most cases, Dockerfiles are written once, modified seldom, and used as-is unless something about the project changes. Because these files are created or modified on such an infrequent basis, developers tend to rely on only a handful of frequently used instructions — RUN
, COPY
, and EXPOSE
being the most common. Other instructions can enhance your image, making it more configurable, manageable, and easier to maintain.
In this post, we will discuss the ARG
and ENV
instructions and explore why, how, and when to use them.
ARG: Defining build-time variables
The ARG
instruction allows you to define variables that will be accessible during the build stage but not available after the image is built. For example, we will use this Dockerfile to build an image where we make the variable specified by the ARG
instruction available during the build process.
FROM ubuntu:latest
ARG THEARG="foo"
RUN echo $THEARG
CMD ["env"]
If we run the build, we will see the echo foo
line in the output:
$ docker build --no-cache -t argtest .
[+] Building 0.4s (6/6) FINISHED docker:desktop-linux
<-- SNIP -->
=> CACHED [1/2] FROM docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e 0.0s
=> => resolve docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> [2/2] RUN echo foo 0.1s
=> exporting to image 0.0s
<-- SNIP -->
However, if we run the image and inspect the output of the env
command, we do not see THEARG
:
$ docker run --rm argtest
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d19f59677dcd
HOME=/root
ENV: Defining build and runtime variables
Unlike ARG
, the ENV
command allows you to define a variable that can be accessed both at build time and run time:
FROM ubuntu:latest
ENV THEENV="bar"
RUN echo $THEENV
CMD ["env"]
If we run the build, we will see the echo bar
line in the output:
$ docker build -t envtest .
[+] Building 0.8s (7/7) FINISHED docker:desktop-linux
<-- SNIP -->
=> CACHED [1/2] FROM docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e 0.0s
=> => resolve docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> [2/2] RUN echo bar 0.1s
=> exporting to image 0.0s
<-- SNIP -->
If we run the image and inspect the output of the env
command, we do see THEENV
set, as expected:
$ docker run --rm envtest
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=f53f1d9712a9
THEENV=bar
HOME=/root
Overriding ARG
A more advanced use of the ARG
instruction is to serve as a placeholder that is then updated at build time:
FROM ubuntu:latest
ARG THEARG
RUN echo $THEARG
CMD ["env"]
If we build the image, we see that we are missing a value for $THEARG
:
$ docker build -t argtest .
<-- SNIP -->
=> CACHED [1/2] FROM docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e 0.0s
=> => resolve docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> [2/2] RUN echo $THEARG 0.1s
=> exporting to image 0.0s
=> => exporting layers 0.0s
<-- SNIP -->
However, we can pass a value for THEARG
on the build command line using the --build-arg
argument. Notice that we now see THEARG
has been replaced with foo
in the output:
=> CACHED [1/2] FROM docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e 0.0s
=> => resolve docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> [2/2] RUN echo foo 0.1s
=> exporting to image 0.0s
=> => exporting layers 0.0s
<-- SNIP -->
The same can be done in a Docker Compose file by using the args
key under the build
key. Note that these can be set as a mapping (THEARG: foo
) or a list (- THEARG=foo
):
services:
argtest:
build:
context: .
args:
THEARG: foo
If we run docker compose up --build
, we can see the THEARG
has been replaced with foo
in the output:
$ docker compose up --build
<-- SNIP -->
=> [argtest 1/2] FROM docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04 0.0s
=> => resolve docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> CACHED [argtest 2/2] RUN echo foo 0.0s
=> [argtest] exporting to image 0.0s
=> => exporting layers 0.0s
<-- SNIP -->
Attaching to argtest-1
argtest-1 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
argtest-1 | HOSTNAME=d9a3789ac47a
argtest-1 | HOME=/root
argtest-1 exited with code 0
Overriding ENV
You can also override ENV
at build time; this is slightly different from how ARG
is overridden. For example, you cannot supply a key without a value with the ENV
instruction, as shown in the following example Dockerfile:
FROM ubuntu:latest
ENV THEENV
RUN echo $THEENV
CMD ["env"]
When we try to build the image, we receive an error:
$ docker build -t envtest .
[+] Building 0.0s (1/1) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 98B 0.0s
Dockerfile:3
--------------------
1 | FROM ubuntu:latest
2 |
3 | >>> ENV THEENV
4 | RUN echo $THEENV
5 |
--------------------
ERROR: failed to solve: ENV must have two arguments
However, we can remove the ENV
instruction from the Dockerfile:
FROM ubuntu:latest
RUN echo $THEENV
CMD ["env"]
This allows us to build the image:
$ docker build -t envtest .
<-- SNIP -->
=> [1/2] FROM docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> => resolve docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> CACHED [2/2] RUN echo $THEENV 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
<-- SNIP -->
Then we can pass an environment variable via the docker run
command using the -e
flag:
$ docker run --rm -e THEENV=bar envtest
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=638cf682d61f
THEENV=bar
HOME=/root
Although the .env
file is usually associated with Docker Compose, it can also be used with docker run
.
$ cat .env
THEENV=bar
$ docker run --rm --env-file ./.env envtest
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=59efe1003811
THEENV=bar
HOME=/root
This can also be done using Docker Compose by using the environment
key. Note that we use the variable format for the value:
services:
envtest:
build:
context: .
environment:
THEENV: ${THEENV}
If we do not supply a value for THEENV
, a warning is thrown:
$ docker compose up --build
WARN[0000] The "THEENV" variable is not set. Defaulting to a blank string.
<-- SNIP -->
=> [envtest 1/2] FROM docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04 0.0s
=> => resolve docker.io/library/ubuntu:latest@sha256:8a37d68f4f73ebf3d4efafbcf66379bf3728902a8038616808f04e34a9ab6 0.0s
=> CACHED [envtest 2/2] RUN echo ${THEENV} 0.0s
=> [envtest] exporting to image 0.0s
<-- SNIP -->
✔ Container dd-envtest-1 Recreated 0.1s
Attaching to envtest-1
envtest-1 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
envtest-1 | HOSTNAME=816d164dc067
envtest-1 | THEENV=
envtest-1 | HOME=/root
envtest-1 exited with code 0
The value for our variable can be supplied in several different ways, as follows:
- On the compose command line:
$ THEENV=bar docker compose up
[+] Running 2/0
✔ Synchronized File Shares 0.0s
✔ Container dd-envtest-1 Recreated 0.1s
Attaching to envtest-1
envtest-1 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
envtest-1 | HOSTNAME=20f67bb40c6a
envtest-1 | THEENV=bar
envtest-1 | HOME=/root
envtest-1 exited with code 0
- In the shell environment on the host system:
$ export THEENV=bar
$ docker compose up
[+] Running 2/0
✔ Synchronized File Shares 0.0s
✔ Container dd-envtest-1 Created 0.0s
Attaching to envtest-1
envtest-1 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
envtest-1 | HOSTNAME=20f67bb40c6a
envtest-1 | THEENV=bar
envtest-1 | HOME=/root
envtest-1 exited with code 0
- In the special
.env
file:
$ cat .env
THEENV=bar
$ docker compose up
[+] Running 2/0
✔ Synchronized File Shares 0.0s
✔ Container dd-envtest-1 Created 0.0s
Attaching to envtest-1
envtest-1 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
envtest-1 | HOSTNAME=20f67bb40c6a
envtest-1 | THEENV=bar
envtest-1 | HOME=/root
envtest-1 exited with code 0
Finally, when running services directly using docker compose run
, you can use the -e
flag to override the .env
file.
$ docker compose run -e THEENV=bar envtest
[+] Creating 1/0
✔ Synchronized File Shares 0.0s
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=219e96494ddd
TERM=xterm
THEENV=bar
HOME=/root
The tl;dr
If you need to access a variable during the build process but not at runtime, use ARG
. If you need to access the variable both during the build and at runtime, or only at runtime, use ENV
.
To decide between them, consider the following flow (Figure 1):
Both ARG
and ENV
can be overridden from the command line in docker run
and docker compose
, giving you a powerful way to dynamically update variables and build flexible workflows.
Learn more
- Discover more Docker Best Practices.
- Subscribe to the Docker Newsletter.
- Get the latest release of Docker Desktop.
- Have questions? The Docker community is here to help.
- New to Docker? Get started.