Using a dynamic Dockerfile can have great benefits when used in your CI/CD pipeline. You can use the ARG
statement in your Dockerfile
to pass in a variable at build time. Even use a variable in the FROM
statement! Dockerfile ARG FROM ARG. That will make more sense later.
This post is about Container virtualization technology, and problems when building a container image using Docker Engine.
For some background on a dynamic Dockerfile, check out Jeff Geerling’s post.
One problem I’ve come across with this, is the Dockerfile
ARG
variable usage isn’t intuitive. It is actually kind of weird. And I’m not alone on this! There’s lots of feedback in GitHub Issue #34129.
What’s the Problem?
A variable declared at the top of the Dockerfile
using ARG
doesn’t work if you try to use it after the FROM
statement. Not by default at least. It only works if used in the FROM
statement.
ARG TAG=latest
FROM myimage:$TAG # <------ This works!
LABEL BASE_IMAGE="myimage:$TAG" # <------ Does not work!
The FROM
above will use the TAG
passed in from a build-arg, or default to use the string “latest”.
But the LABEL
won’t work. The TAG
portion will be empty!
This was tested with the most recent version of Docker Engine (version: 19.03.5).
How Does it Work?
To use a variable in the FROM
section, you need to put the ARG
before the FROM
. So far so good. But, now let’s use that variable again after the FROM
? No you can’t. It’s not there. Unless you use another ARG
statement after the FROM
.
Wait, it gets more weird. Any ARG
before the FIRST FROM
, can be used in any FROM
(for multi-stage builds). In fact the ARG
must be above the first FROM
even if you only want to use it in a later FROM
. Wow, that’s a lot of ARG
s and FROM
s. The ARG
is kind of “global” but only if you declare it again inside the build stage.
Dockerfile ARG FROM ARG
Let’s look at an example to make it easier. Below is not a useful Dockerfile. It’s only an example to illustrate this.
#These ARGs are used in the FROM statements
#Or as global variables if declared again (without the default value)
ARG BUILDER_TAG=latest
ARG BASE_TAG=latest
ARG APP="app.go"
#First build stage
FROM mybuildapp:$BUILDER_TAG AS builder
ARG APP
RUN compile_my_app $APP
#Second build stage
FROM registry.access.redhat.com/ubi8/ubi-minimal:$BASE_TAG
ARG BASE_TAG
ARG APP
LABEL BASE_IMAGE="registry.access.redhat.com/ubi8/ubi-minimal:$BASE_TAG"
COPY --from=builder $APP .
Now, let’s run the example build:
docker build . \
--pull
--build-arg BUILDER_TAG="2.0" \
--build-arg BASE_TAG="8.1" \
--build-arg APP="the_best_app.go" \
-t image_tag
Once the Docker build finishes, this is the result of your command line argument usage inside the docker image:
BUILDER_TAG="2.0"
BASE_TAG="8.1"
APP="the_best_app.go"
Results
The BUILDER_TAG
and BASE_TAG
will be used in the FROM
statements. BASE_TAG
is also used inside the second build stage. And APP
will be used in both build stages. ARG APP="app.go"
only needs to be declared if you want to set a default value. In my example we don’t really need that since we are passing in a build argument.
Conclusion
Remember that ARG
s at the top of the Dockerfile
can be used in the FROM
statements. They are “global” only if you declare the ARG
again in each build stage (after FROM
(and before the next FROM
if using a multi-stage build)).
You can remember it this way: Dockerfile
ARG
FROM
ARG
But, after wasting an hour on this, it was more like: Dockerfile Arrggh! FROM Arrggh!
You can use something like this in your CI/CD, for automatic docker image building.
Side note: Building an image using Podman does not have the same issue as Docker Engine (tested with Podman version 1.4.4, 1.6.3, and 1.7.0). You do not need to specify the ARG
again inside the build stage when Podman creates the container image!
Further reading:
Dockerfile Reference: Understand how ARG and FROM interact
Dockerfile Reference: Scope
Multi-Stage Builds