Using variable interpolation in string in Docker

I requested @Villem to answer, and his answer is much more definitive, but the following will work (just is not a stable solution). His answer is basically saying that this answer is not a good way to do it:

ARG s           # passed in via --build-arg s=foo
ARG sname       # passed in via --build-arg sname=bar

RUN echo $s       
RUN echo $sname  

ENV sname $sname   # this is the key part

RUN useradd -ms /bin/bash newuser

USER newuser
WORKDIR /home/newuser

COPY $s .

ENTRYPOINT /bin/bash /home/newuser/$sname   # works, but is not stable!

don't ask me why the COPY command picks up the variable that was declared via ARG, but that the ENTRYPOINT command does not seem to pick up the variable declared via ARG, but only picks up the variable declared via ENV. At least, this appears to be the case.


I spent a lot of time to find out that.

Don't works !

ARG install="bundle install --jobs=4"
FROM ruby:2.6.3-alpine

RUN eval $install

But this works...

FROM ruby:2.6.3-alpine

ARG install="bundle install --jobs=4"
RUN eval $install

Then to build the docker image:

docker build -t server --no-cache --build-arg install="bundle install --without development test" .`

I would avoid using variable in ENTRYPOINT at all. It's tricky and requires a deep understanding of what is going on. And is easy to break it by accident. Just consider one of the following.

Create link with the known name to your start script.

RUN ln -s /home/newuser/$sname /home/newuser/docker_entrypoint.sh
ENTRYPOINT ["/home/newuser/docker_entrypoint.sh"]

or write standalone entrypoint script that runs what you need.

But if you want to know how and why solutions in your questions work just keep reading.

First some definitions.

  • ENV - is environment variable available during buildtime (docker build) and runtime (docker run)
  • ARG - is environment variable available only during buildtime

If you look at https://docs.docker.com/engine/reference/builder/#environment-replacement you see the list of dockerfile instructions that support those environment variables directly. This is why COPY "picks up the variable" as you said.

Please note that there is no RUN nor ENTRYPOINT. How does it work?

You need to dig into the documentation. First RUN (https://docs.docker.com/engine/reference/builder/#run). There are 2 forms. The first one executes command through the shell and this shell has access to buildtime environment variables.

# this works because it is called as /bin/sh -c 'echo $sname'
# the /bin/sh replace $sname for you      
RUN echo $sname 

# this does NOT work. There is no shell process to do $sname replacement 
# for you
RUN ["echo", "$sname"]

Same thing applies to the ENTRYPOINT and CMD except only runtime variables are available during container start.

# first you need to make some runtime variable from builtime one
ENV sname $sname

# Then you can use it during container runtime
# docker runs `/bin/sh -c '/bin/bash /home/newuser/$sname'` for you
# and this `/bin/sh` proces interprets `$sname`
ENTRYPOINT /bin/bash /home/newuser/$sname

# but this does NOT work. There is no process to interpolate `$sname`
# docker runs what you describe.
ENTRYPOINT ["/bin/bash", "/home/newuser/$sname"]

edit 2017-04-03: updated links to the docker documentations and slight rewording to avoid confusion that I sense from other answers and comments.


I wanted both variable substitution and arguments passing.

Let's say our Dockerfile has:

ENV VARIABLE=replaced

And we want to run this:

docker run <image> arg1 arg2

I obviously wanted this output:

replaced arg1 arg2

I eventually found this one:

ENTRYPOINT [ "sh", "-c", "echo $VARIABLE $0 $@" ]

It works!!! But I feel SOOOO dirty!

Obviously, in real life, I wanted to do something more useful:

docker run <image> --app.options.additional.second=true --app.options.additional.third=false

ENTRYPOINT [ "sh", "-c", "java -Xmx$XMX $0 $@", \
             "-jar", \
             "/my.jar", \
             "--app.options.first=true" ]

Why a so complicated answer?

  • "ENTRYPOINT java..." would not pass docker arguments to the entrypoint => "ENTRYPOINT [..." is mandatory for that
  • "ENTRYPOINT [..." will NOT call the shell, so no variable substitution is done at all => "sh -c" is mandatory for that
  • "sh -c" only take the FIRST argument passed to it and split it in command+arguments => so everything must be in the first argument of "sh -c" for variables to be visible by the command
  • Docker arguments are passed as extra array entries of "ENTRYPOINT [..." => so the "$@" is necessary to "copy" the remainings arguments of the "sh -c" into the "echo ..." command to be executed (and as a bonus, we can reuse the additional array entries of ENTRYPOINT[] to place forced arguments in a readable way in the Dockerfile)
  • "$@" removes the first argument => so explicit "$0" must be used before "$@"

Fiou...

I added a comment to this issue, for Docker developers to see what we are forced to do and perhaps change their mind to make ENTRYPOINT[] replace environment variables: https://github.com/moby/moby/issues/4783#issuecomment-442466609

Tags:

Docker

Bash