Docker ARGs
Today I realised that there is more depth to the ARG docker command than I had anticipated. Especially its scope in the context of multi-stage builds is non-trivial. Take the following (highly convoluted) Dockerfile for example:
ARG VERSION=latest FROM alpine:$VERSION as build ARG VERSION=3.16 RUN echo $VERSION > image_version FROM alpine:$VERSION COPY --from=build /image_version /other_image_version ARG VERSION RUN echo $VERSION > image_version CMD cat other_image_version && cat image_version
The first stage will use alpine:latest as base image. VERSION then gets overwritten to 3.16 and this value is saved to file image_version.
Which alpine tag will the second stage use? Because ARG commands within a stage are local to that stage, the ARG VERSION=3.16 line has no effect on this FROM statement. Instead, the previous value latest will be used.
This second stage retrieves the version file from the first stage and also creates its own image_version file. Which value will be written to this file? We defined an argument VERSION without value in this stage, but that does not mean that the argument will be empty. Rather, this line allows the ARG VERSION=latest statement on line 1 to enter the scope of the second stage. As a consequence, the value latest will be written to the image_version file. If we had skipped the ARG VERSION line, $VERSION would have been undefined and the image_version file would have been empty.
Let's confirm our theory:
$ docker build -t argtest . [+] Building 1.0s (8/8) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 320B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/alpine:latest 0.0s => [build 1/2] FROM docker.io/library/alpine:latest 0.0s => [build 2/2] RUN echo 3.16 > image_version 0.3s => [stage-1 2/3] COPY --from=build /image_version /other_image_version 0.0s => [stage-1 3/3] RUN echo latest > image_version 0.5s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:667654e8ba78edf07a9d64a3fb7576fdfb6a4be421b... 0.0s => => naming to docker.io/library/argtest 0.0s $ docker run --rm argtest 3.16 latest
When we manually specify a value for the build argument, it is applied everywhere:
$ docker build -t argtest --build-arg VERSION=edge . $ docker run --rm argtest edge edge
Fun fact
ARGs are not secret. The values passed during docker build can be retrieved from an image with the docker history command. Do not use ARGs to pass sensitive information such as passwords. Use RUN --mount=type=secret instead.
Summary
-
ARGs defined before the first stage- can only be used in
FROMstatements - can be imported in the scope of a stage by re-declaring them without value
- can only be used in
-
ARGs defined within a stage- are scoped to the subsequent lines of that stage
- shadow any outside
ARGs with the same name
-
ARGs are unsuited for secrets