Docker environment substitution with environment variable replacement - docker

I'm trying to use environment variable substitution inside a Dockerfile paired with environment variable replacement from docker but it looks like the variable replacement takes place after the substitution.
The following Dockefile:
FROM alpine:3.7
ENV name="World"
ENV message="Hello, ${name}"
ENTRYPOINT ["env"]
With the Docker run command:
$ docker run -it --rm -e "name=Marvin" envtest/helloworld
Prints the following environment variables:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=49d702faf257
TERM=xterm
name=Marvin
message=Hello, World
HOME=/root
You can see that even though I replaced the name variable, the message variable is still substituted with the original value from name.
Does anyone know how to do environment variable substitution with dockers environment variable replacement?
EDIT: I found a comment in the Docker forum stating that environment variables are interpreted at build time but can be replaced one by one at runtime. So the documentation is a little misleading.

When you build the Dockerfile, you get an image. And the image has no knowledege about what was written in the Dockerfile.
That means, the docker image has no knowledge weather ENV message="Hello, ${name}" or ENV message="Hello, world" was written in the Dockerfile.
It just has it's environment variable as it is, i.e., name="World", message="Hello, world"
So, when you start your image using $ docker run -it --rm -e "name=Marvin" envtest/helloworld, it overrides the variable name, i.e. now name is Marvin.
But message remains "Hello, world".
Because, inside the image, message is "Hello, world" not "Hello, ${name}"

Related

Dockerfile: reference previously defined ENV in another ENV

I have a Dockerfile in which I set some environment variables (for use when I run the container). Some of the environment variables depend on previous environment variables. I want to be DRY and avoid having to hard-code the value of environment variables multiple times, when I could substitute in a variable.
In this simple example, the PYTHONPATH environment variable uses values from the PROJ_DIR environment variable.
FROM python:3.8.4
ENV PROJ_DIR=/myproj/ \
PYTHONPATH=${PROJ_DIR}:${PYTHONPATH}
However, when I actually run the container, the PROJ_DIR correctly gets set, but the dependent variable, PYTHONPATH, does not get set.
docker build -f Dockerfile . -t test-docker
docker run --rm -it test-docker:latest bash
root#60fc899899a1:/# export | grep -i proj
declare -x PROJ_DIR="/myproj/"
root#60fc899899a1:/# export | grep -i pythonpath
declare -x PYTHONPATH=":"
How do I use previously set environment variables in a Dockerfile?
For this to work you would need to spearate out the variables and use multiline assignment.
ENV PROJ_DIR /myproj/
ENV PYTHONPATH ${PROJ_DIR}:${PYTHONPATH}
Throughout the entire instruction, environment variable substitution will use the same value for each variable. In your case PROJ_DIR is yet to be assigned a value, so it returns empty in PYTHONPATH varaible.
To be more clear, in:
ENV x=hello
ENV x=world z=$x
z will have value hello and not world.
Due to multiline there will be not be any additonal layers getting created as ENV layers do get squashed.
Hope that helps.
You can reuse environment variables that were previously set outside but not inside an ENV, since all statements in the same ENV pick up the value defined before the ENV is processed. So you should split it into separate ENVs.
ENV PROJ_DIR=/myproj/
ENV PYTHONPATH=${PROJ_DIR}:${PYTHONPATH}

Pass ENV in docker run command

Is there a way we can pass a variable lets say in this example I want to pass a list of animals into an entrypoint.sh file using ENV animals="turtle, monkey, goose"
But I want to be able to pass different animals when running the container for example docker run -t image animals="mouse,rat,kangaroo"
How do you go about passing arguments when running the docker run command?
The goal is to take that variable when using the docker run command and insert them into that entrypoint.sh file
Right now i hard code that in my Dockerfile. But i want to be able to do this when running the docker run command so I dont always have to change the Dockerfile.
FROM anapsix/alpine-java:8u121b13_jdk
ENV FILE_NAME="file_to_run.zip"
ENV animals="turtle, monkey, goose"
ADD ${FILE_NAME} .
RUN echo "${FILENAME} ${animals}" > ./entrypoint.sh
CMD [ "/bin/ash", "./entrypoint.sh" ]
It looks like you might be confusing the image build with the container run. If the difference between the two isn't immediately clear, I'd recommend reviewing some other questions and docs like:
In Docker, what's the difference between a container and an image?
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
RUN echo "${FILENAME} ${animals}" > ./entrypoint.sh
With the above, the variables will be expanded during the image build. The entrypoint.sh will not contain ${FILENAME} ${animals}. Instead, it will contain
file_to_run.zip turtle, monkey, goose
After the build, the docker run command will create a container from that image and run the above script with the environment variables defined but never used since the script already has the variables expanded. To prevent the variable expansion, you need to escape the $ or use single quotes to prevent the expansion, e.g.
RUN echo "\${FILENAME} \${animals}" > ./entrypoint.sh
or
RUN echo '${FILENAME} ${animals}' > ./entrypoint.sh
I would also recommend being explicit with a #!/bin/ash at the top of this script. Then when you run the script, do not override the command with parameters after the image name. Instead set the environment variables with the appropriate flag to run:
docker run -it -e animals="mouse,rat,kangaroo" image
Simplest way, forward individual variables:
docker run ... --env animals="turtle, monkey, goose" --env FILE_NAME="file_to_run.zip"
Forward several variables using file:
Or if you need to grab all your environment variables from outside, you can do something like this first:
printenv | grep -E 'animals|FILE_NAME' > my-env
The grep is because Docker doesn't like some variables, e.g. with spaces in them, which you might possibly have in your real environment.
Then use that file in your Docker command:
docker run ... --env-file ./my-env
The latter is also useful if you want to avoid sending environment variables to logs (like for sensitive variables). I use this approach in a CI/CD pipeline that runs some scripts.
Using variables inside Docker:
With either approach, the environment variables actually become available to scripts running inside the container to use.
#BMitch's answer has more complete details about how to achieve this in your case, where you have related logic in both build and execution.
Reference
See docs here.

Passing Different Arguments When Running Docker Image Multiple Times

I need to give an argument while running Docker Image which will be a number from 0-3.
Dockerfile has the following:
WORKDIR "mydir/build"
CMD ./maker oneapp > /artifacts/oneapp_$1.log ; ./maker twoapp > /artifacts/twoapp_$1.log ; ./maker -j13 threeapp > /artifacts/threeapp_$1.log
I will be running the same Docker Image multiple times so I need logs to be saved in /artifacts appended with _0, _1, _2, _3, as appropriate.
I tried keeping this in Docker file but don't want to pass this full line as argument while running docker.
ENTRYPOINT ["/bin/bash"]
./maker oneapp > /artifacts/oneapp_$1.log ; ./maker twoapp >
/artifacts/twoapp_$1.log ; ./maker -j13 threeapp >
/artifacts/threeapp_$1.log
Is it possible to do this? What do I need to modify in Dockerfile to do what I want?
Simply inject your parameter as an ENV.
Declare an ENV in your Dockerfile.
ENV suffix 0
./maker oneapp > /artifacts/oneapp_${suffix}.log
The environment variables set using ENV will persist when a container is run from the resulting image.
You can view the values using docker inspect, and change them using docker run --env <key>=<value>.
That way, you can declare that ENV on docker run, and benefit from its value in the running container.
the operator can set any environment variable in the container by using one or more -e flags, even overriding those mentioned above, or already defined by the developer with a Dockerfile ENV:
In your case, for instance:
docker run -e suffix=2 <image_name>

Is there an alternative to Dockerfile Env Instruction?

In their official site (https://docs.docker.com/reference/builder/#env), Docker support state that:
The ENV instruction sets the environment variable to the value
. This value will be passed to all future RUN instructions.
This is functionally equivalent to prefixing the command with < key >=< value >
I tried:
http_proxy=<PROXY> docker build .
However, this doesn't seem to bring the same effect as adding ENV http_proxy=< PROXY > inside the Dockerfile. Why ???
This is functionally equivalent to prefixing the command with < key >=< value >
This does not mean it is the same as prefixing docker build command since It is command executed outside of a container.
It means using ENV is the same as prefixing commands that run inside container.
For example, equivalent RUN statement would look like this:
RUN http_proxy=<PROXY> curl https://www.google.com
Or equivalent command executed inside container (via shell):
$ http_proxy=<PROXY> curl https://www.google.com

Docker echo environment variable

I'm trying to write a little docker file that sets a User and just echos the current user as a little example to prove to myself it is working. I've tried a number of variants and couldn't find much help in the documentation.
FROM ubuntu
USER daemon
# ENTRYPOINT ["echo", "$USER"]
# just gives "$USER"
# ENTRYPOINT ["echo", "-e", "${USER}"]
# just gives "$USER"
# ENTRYPOINT echo $USER
# gives empty string
# ENTRYPOINT ["/bin/echo", "$USER"]
# just gives "$USER"
I'm running docker build . on the dockerfile and then running docker run <image-id> and getting the results
Expected result is daemon, or without the USER daemon line, I expect root. Probably a really simple answer.
This is the expected behavior, as weird as it seems!
When ENTRYPOINT is a list (as in ENTRYPOINT ["echo", "$USER"]), it is used as-is, without further parsing or interpretation. So $USER remains $USER, because there is no shell involved in the process to replace it with the value of the USER environment variable.
Now, when ENTRYPOINT is a string (as in ENTRYPOINT echo $USER), what is actually executed is sh -c "echo $USER", and $USER is replaced with the value of the environment variable (as you would expect).
However, the environment variable USER is not set by default. It is set by the login process; and when you just run sh -c ... the login process is not involved.
Compare the environment when running docker run -t -i ubuntu bash and docker run -t -i ubuntu login -f root. In the former case, you will get a very basic environment; in the latter case, you will get the complete environment that you are used to (including USERvariable).
Couldn't you set, in the Dockerfile, the ENV command to a default value, and then, when run-ning a container, use the -e, --env dictionary to override what would be interpreted by the:
ENTRYPOINT echo $SOMEENVVAR
form of ENTRYPOINT?
I think there´s a series of issues here.
when I
docker run -i -t ubuntu /bin/bash
echo $USER
set
I don´t see $USER set at all - whoami does report daemon though.
additionally, I have the suspicion (but have not looked at the code yet) that ENV vars in the Dockerfile are escaped, to avoid their use (many people assume that they can export host variables to the built container, but this is something that the docker guys would like to avoid)

Resources