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.
One problem I’ve come across with this, is the
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
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
ARG TAG=latest FROM myimage:$TAG # <------ This works! LABEL BASE_IMAGE="myimage:$TAG" # <------ Does not work!
FROM above will use the
TAG passed in from a build-arg, or default to use the string “latest”.
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
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 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"
BASE_TAG will be used in the
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.
ARGs 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:
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!