Docker ARG tricky behavior - docker

Docker build tricky behavior:
Dockerfile
ARG php_version="7.2"
ARG flavor="stretch"
FROM php:${php_version}-fpm-${flavor}
#ARG php_version="7.2"
ENV php_v $php_version
CMD echo $php_v
If you build it and run:
docker build -t args:1.0 .
docker run -it --name testargs args:1.0
Output is empty string instead of the expected "7.2"
But if the third ARG line is uncommented we get "7.2"
Why does it behave this way?

Each FROM in a Dockerfile represents a new build stage and an ARG declared before the FROM is not available for use in the newer build stages starting with another FROM.
To quote the relevant section of the doc:
An ARG declared before a FROM is outside of a build stage, so it can’t
be used in any instruction after a FROM.

Related

docker pass build arg to the shell script which needs to be run during the docker building

The problem is i cannot get the docker build arg value in the shell script while running the docker build.
My docker build command:
DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain \
-t test \
--build-arg WHL_PATH=/fake/path \
.
Dockerfile
ARG WHL_PATH
FROM python:3.8.8
COPY test.sh .
RUN ./test.sh $WHL_PATH
and in the test.sh the "$1" is empty...., if in the Dockerfile i put some constant value then i will be able to see that value in the $1, but with docker build arg or set the build arg as ENV VAR are always empty...
Where am i doing wrong, how should i achieve this?
Docker version 20.10.5, build 55c4c88
Build args are scoped. Before the first FROM step they only apply to the FROM steps to adjust the image you use. Within each stage, an ARG step applies to the remaining steps within that stage. So the fix is to reorder your steps:
FROM python:3.8.8
COPY test.sh .
ARG WHL_PATH
RUN ./test.sh $WHL_PATH
Oops, i never realised the position of the ARG instruction matters, basically:
any ARG before the first FROM can be used in any FROM line
any ARG within a build stage (after a FROM) can be used in that build stage
After i moved the ARG WHL_PATH after the line FROM xxx it works perfectly, hope this can save some of your time in the future.
And i was inspired by this answer actually: https://stackoverflow.com/a/50292255/7658313

Select ENV/ARG in Dockerfile depending on build ARG

I currently have two Dockerfiles that are identical except for some ENV vars at the beginning that have different values. I want to combine them into one Dockerfile and select the ENV vars depending on one build-arg / ARG instead.
I tried something like this with $target being either WIN or UNIX:
FROM alpine
ARG target
ARG VAR1_WIN=Value4Win
ARG VAR1_UNIX=Value4Unix
ARG VAR1=VAR1_$target
ARG VAR1=${!VAR1}
RUN echo $VAR1
But it throws an error: failed to process "${!VAR1}": missing ':' in substitution
I tried a lot but I'm unable to double expand $VAR1.
How do I do this correctly? Thx.
For the conditional syntax, there is a pattern you can use with a multi-stage build:
# ARG defined before the first FROM can be used in FROM lines
ARG target
# first base image for WIN target
FROM alpine as base-WIN
# switching to ENV since ARG doesn't pass through to the next stage
ENV VAR1=Value4Win
# second base image for UNIX target
FROM alpine as base-UNIX
ENV VAR1=Value4Unix
# select one of the above images based on the value of target
FROM base-${target} as release
RUN echo $VAR1
The double expand syntax ${!VAR} only works in bash, while Dockerfile is not parsed by shell. According to the docker manual, Dockerfile does not support double expand.
Note that alpine use ash instead of bash, so it does not support ${!VAR} either. You have to use eval + echo. Try RUN VAR1="$(eval "echo \$$VAR1")"; echo $VAR1.
If you can't pass Value4Win or Value4Unix directly via --build-arg, here is one way:
FROM alpine
ARG target
ARG VAR1=Value4${target}
RUN echo $VAR1
Doing a docker build --build-arg target=WIN . gives:
Step 4/4 : RUN echo $VAR1
---> Running in 6e94bc28d459
Value4WIN

docker: Variables in two stage build

I am using a two stage build for a docker image;
I want to make a value in my second stage configurable, so I was thinking of using ARG.
However I am not sure the --build-arg command line option applies to other than the first stage, so I came up with this
### First stage
FROM some_base_image
ARG MYUSERNAME=foo
### Second stage
FROM another_base_image
ARG MYUSERNAME=$MYUSERNAME
but that didn't work;
any suggestions how to pass via the build command line some --build-args that should be usable from the second stage?
ARG instructions have a scope, it's described here in the docs: https://docs.docker.com/engine/reference/builder/#scope
An ARG instruction goes out of scope at the end of the build stage where it was defined. To use an arg in multiple stages, each stage must include the ARG instruction.
So you can use an ARG in multiple stages, you just need to use the ARG instruction again as shown in the example:
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

How to change a docker ARG value inside an if statement?

In my Dockerfile on Ubuntu 16.04 with docker 17.12.1-ce I use
ARG ver=latest
ARG pkg=master
For building a docker container, I would like to call docker build --build-arg ver=v1 . in order to set a special package.
The code-handling part in my Dockerfile is
RUN if[ "x$ver" = "xv1" ] ; then pkg=v1.2.3 ; fi
RUN echo $pkg
Unfortunately, the ARG pkg variable is not updated and the echo statement always shows its initial value.
What can I do, to update my build variable pkg inside an if statement??
You will not be able to pass variables from one RUN to another because each RUN command is executed in a different shell.
A solution to your problem would be to extract the logic into a script and execute docker with the build arguments something like this:
if [ $ver="v1" ]; then pkg=1.2.3; fi; docker build --build-arg ver=$ver --build-arg pkg=$pkg .

ARG substitution in RUN command not working for Dockerfile

In my Dockerfile I have the following:
ARG a-version
RUN wget -q -O /tmp/alle.tar.gz http://someserver/server/$a-version/a-server-$a-version.tar.gz && \
mkdir /opt/apps/$a-version
However when building this with:
--build-arg http_proxy=http://myproxy","--build-arg a-version=a","--build-arg b-version=b"
Step 10/15 : RUN wget... is shown with $a-version in the path instead of the substituted value and the build fails.
I have followed the instructions shown here but must be missing something else.
My questions is, what could be causing this issue and how can i solve
it?
Another thing to be careful about is that after every FROM statements all the ARGs get collected and are no longer available. Be careful with multi-stage builds.
You can reuse ARG with omitted default value inside FROM to get through this problem:
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
Example taken from docs:
https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
Don't use - in variable names.
Docker build will always show you the line as is written down in the Dockerfile, despite the variable value.
So use this variable name a_version:
ARG a_version
See this example:
Dockerfile:
FROM alpine
ARG a_version
RUN echo $a_version
Build:
$ docker build . --build-arg a_version=1234
Sending build context to Docker daemon 2.048 kB
Step 1/3 : FROM alpine
---> a41a7446062d
Step 2/3 : ARG a_version
---> Running in c55e98cab494
---> 53dedab7de75
Removing intermediate container c55e98cab494
Step 3/3 : RUN echo $a_version <<< note this <<
---> Running in 56b8aeddf77b
1234 <<<< and this <<
---> 89badadc1ccf
Removing intermediate container 56b8aeddf77b
Successfully built 89badadc1ccf
I had the same problem using Windows containers for Windows.
Instead of doing this (Which works in linux containers)
FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
You need to do this
FROM mcr.microsoft.com/windows/servercore
ARG TARGETPLATFORM
RUN echo "I'm building for %TARGETPLATFORM%"
Just change the variable resolution according to the OS.
I spent much time to have the argument substitution working, but the solution was really simple. The substitution within RUN needs the argument reference to be enclosed in double quotes.
ARG CONFIGURATION=Debug
RUN dotnet publish "Project.csproj" -c "$CONFIGURATION" -o /app/publish
The only way I was able to substitute an ARG in a Windows Container was to prefix with $env:, as mentioned here.
An example of my Dockerfile is below. Notice that the ARG PAT is defined after the FROM so that it's in scope for its use in the RUN nuget sources add command (as Hongtao suggested). The only successful way I found to supply the personal access token was using $env:PAT
FROM mcr.microsoft.com/dotnet/framework/sdk:4.7.2 AS build
WORKDIR /app
ARG PAT
# copy csproj and restore as distinct layers
COPY *.sln .
COPY WebApi/*.csproj ./WebApi/
COPY WebApi/*.config ./WebApi/
RUN nuget sources add -name AppDev -source https://mysource.pkgs.visualstudio.com/_packaging/AppDev/nuget/v2 -username usern -password $env:PAT
RUN nuget restore
# copy everything else and build app
COPY WebApi/. ./WebApi/
WORKDIR /app/WebApi
RUN msbuild /p:Configuration=Release
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.7.2 AS runtime
WORKDIR /inetpub/wwwroot
COPY --from=build /app/WebApi/. ./
The actual Docker command looks like this:
docker build --build-arg PAT=mypatgoeshere -t webapi .
I had the same problem accessing build-args in my RUN command. Turns out that the line containing the ARG definition should not be the first line. The working Dockerfile snippet looks like this:
FROM centos:7
MAINTAINER xxxxx
ARG SERVER_IPS
Earlier, I had placed the ARG definition as the first line of Dockerfile . My docker version is v19.
There are many answers, which make sense.
But the main thing is missed.
The way, how to use build arguments depends on the base image.
For Linux image, it will work with $ARG
For Windows, depending on image, it can be either $env:ARG(e.g. for mcr.microsoft.com/dotnet/framework/sdk:4.8) or %ARG% (e.g. for mcr.microsoft.com/windows/nanoserver:1809)
For me it was argument's order:
docker build . -f somepath/to/Dockerfile --build-arg FOO=BAR
did not work, but:
docker build --build-arg FOO=BAR . -f somepath/to/Dockerfile
did.

Resources