Docker ENV in CMD - docker

I am trying to do the following:
Pass a build arg to my docker build command
Store that as an env variable in my container
Use it in my CMD to start when my container launches.
Below is my setup:
FROM ubuntu:xenial
ARG EXECUTABLE
ENV EXECUTABLE ${EXECUTABLE}
CMD ["/opt/foo/bin/${EXECUTABLE}", "-bar"]
Here is how i'm building container
docker build --build-arg EXECUTABLE=$EXECUTABLE -t test_image .
Here is how i'm running image
docker run -d test_image
When I run the container it crashes and tells me
docker: Error response from daemon: OCI runtime create failed:
container_linux.go:296: starting container process caused
"exec: \"/opt/foo/bin/${EXECUTABLE}\": stat /opt/foo/bin/${EXECUTABLE}:
no such file or directory": unknown.

To use environment variables, you need to use shell.
https://docs.docker.com/engine/reference/builder/#cmd
Note: Unlike the shell form, the exec form does not invoke a command
shell. This means that normal shell processing does not happen. For
example, CMD [ "echo", "$HOME" ] will not do variable substitution on
$HOME. If you want shell processing then either use the shell form or
execute a shell directly, for example: CMD [ "sh", "-c", "echo $HOME"
]. When using the exec form and executing a shell directly, as in the
case for the shell form, it is the shell that is doing the environment
variable expansion, not docker.
Based on this, I think you can work fine by the following Dockerfile.
FROM ubuntu:xenial
ARG EXECUTABLE
ENV EXECUTABLE ${EXECUTABLE}
CMD [ "sh", "-c", "/opt/foo/bin/${EXECUTABLE}", "-bar"]

You'll have to write out an executable or shim as ARG / ENV substitution is not supported for CMD.
The list of supported substitutions:
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
as well as:
ONBUILD (when combined with one of the supported instructions above)
A workaround is to write your executable to a file and execute that:
FROM ubuntu:xenial
ARG EXECUTABLE
RUN : \
&& /bin/echo -e "#!/bin/sh\nexec '/bin/$EXECUTABLE' -bar" > /exe \
&& chmod +x /exe
CMD ["/exe"]
Build:
docker build -t test --build-arg EXECUTABLE=echo .
Run:
$ docker run -ti test
-bar

Another way to access the environment variable passed is by running it as:
docker run -e EXECUTABLE=<some_value> <docker_image>
Then, in the dockerfile
CMD exec /opt/foo/bin/${EXECUTABLE} -bar

Related

Docker from scratch CMD bug

I have the following Dockerfile
FROM golang as builder
ARG CADDY_HASH=4b4e99bdb2e327d553a5f773f827f624181714af
WORKDIR /root/caddy
RUN wget -qO- github.com/caddyserver/caddy/archive/"$CADDY_HASH".tar.gz | tar zx --strip-components=1
RUN set -e; cd cmd/caddy && CGO_ENABLED=0 go build
FROM scratch
COPY --from=builder /root/caddy/cmd/caddy/caddy /
ARG PORT=8000
ENV PORT $PORT
EXPOSE $PORT
CMD /caddy file-server --browse --listen :$PORT
I build and run with this command
DOCKER_BUILDKIT=0 docker build -t caddy-static-docker:latest . && docker run -e PORT=8000 -p 8000:8000 caddy-static-docker:latest
Why this won't work and I receive this error?
docker: Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/bin/sh": stat /bin/sh: no such file or directory: unknown.
Use an entrypoint instead of CMD
ENTRYPOINT ["/caddy"]
CMD ["file-server", "--browse", "--listen", "8080"]
Also note the json syntax (exec form), which leads to the things not run in a subshell.
Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, CMD [ "echo", "$HOME" ] will not do variable substitution on $HOME. If you want shell processing then either use the shell form or execute a shell directly, for example: CMD [ "sh", "-c", "echo $HOME" ]. When using the exec form and executing a shell directly, as in the case for the shell form, it is the shell that is doing the environment variable expansion, not docker.
source: https://docs.docker.com/engine/reference/builder/#cmd
The shell form prevents any CMD or run command line arguments from being used, but has the disadvantage that your ENTRYPOINT will be started as a subcommand of /bin/sh -c, which does not pass signals. This means that the executable will not be the container’s PID 1 - and will not receive Unix signals - so your executable will not receive a SIGTERM from docker stop .
Source: https://docs.docker.com/engine/reference/builder/#entrypoint
Your PORT will still cause issues. Consider hard coding it.
Use a alpine image which is a lightweight image to run shell commands and it has /bin/sh loaded in it.
The SCRATCH image is basically empty with nothing inside / folder, thus no executables to execute anything that is given as part of CMD.

How to pass an unknown list of environment variables to a command in Dockerfile

I have a very long and often changing list of environment variables which I need to pass to the same Docker image when starting it. These environment variables are configured in a Rancher environment and will be passed individually as such. They should all be passed to the command that is about to start within the image.
When I had just a few parameters it was possible to pass them while having them explicitly declared in the Dockerfile:
CMD [ "sh", "-c", "node src/server.js --param1=$ENV_PARAM_1" --param2=$ENV_PARAM_2 ... --paramN=$ENV_PARAM_N"" ]
Now this is not possible anymore because the list has grown to far and is dynamically changing a lot. I also can't build a new image per usecase.
I need something like:
CMD [ "sh", "-c", "node src/server.js $PRINT_ALL_MY_PARAMS_HERE" ]
Side note: The command will fail when providing command arguments that are unknown to the command.
Any idea how I could solve this?
You can override CMD when you run the container. Say you've built an image with a default command
CMD node src/server.js
When you go to actually run the container, you can override this with whatever you want
docker run \
-d -p ... \
my/image \
node src/server.js --param1=$ENV_PARAM_1 --param2=$ENV_PARAM_2 ...
As I've written it here the $ENV_PARAM_N will be resolved by the host system's shell, but if a tool is launching the container for you that might not be a problem. If some of the values are from Dockerfile ENV directives you'll need to force the container shell to do the expansion
docker run \
-d -p ... \
-e ENV_PARAM_2=not-in-the-dockerfile \
my/image \
sh -c 'node src/server.js --param1=$ENV_PARAM_1 --param2=$ENV_PARAM_2 ...'
There's also a pattern of using the ENTRYPOINT as the main program to run and using CMD only for additional options.
ENTRYPOINT ["node", "src/server.js"]
CMD []
docker run \
-d -p ... \
my/image \
--param1=$ENV_PARAM_1 --param2=$ENV_PARAM_2 ...
However, note in this case that you cannot ask the container shell to expand things for you. ENTRYPOINT must use the JSON-array syntax, and you can't insert an sh -c anywhere in this command usefully. (sh -c command consumes only a single shell "word" as its command, and any other options you write after that will generally get ignored.)
You could use ENTRYPOINT to define the part that should always be there when launching the container and CMD for the part that is overridden by command given at container launch:
ENTRYPOINT [ "sh", "-c", "node src/server.js"]
CMD ["--param1=$ENV_PARAM_1", "--param2=$ENV_PARAM_2",... "--paramN=$ENV_PARAM_N"]
This way you can have different parameters for each run:
docker run server # Executes ENTRYPOINT + CMD from Dockerfile
docker run server --help # Executes ENTRYPOINT + "--help"

Unable to connect interactively to container when using entrypoint script in Dockerfile

I need to initialize environment variables when running a container by the script.
So write a shell script to initialize it when running a container by assign it to entrypoint.
entrypoint.sh
#!/bin/bash
# Load the script of environment variables
. /home/foo/setupvars.sh
echo "INITIALIZZEE"
printenv
exec "$#"
Dockerfile
FROM ubuntu:18.04
WORKDIR /home/foo
COPY entrypoint.sh /home/foo/entrypoint.sh
RUN ["chmod", "+x", "/home/foo/entrypoint.sh"]
COPY setupvars.sh /home/foo/setupvars.sh
RUN ["chmod", "+x", "/home/foo/setupvars.sh"]
ENTRYPOINT ["/home/foo/entrypoint.sh"]
setupvars.sh
I build the image,
docker build -t foo .
and run a container like:
docker run --rm -it foo bash
When run a container, environment variable script run, echo run, printenv run. But I can't get any interactive bash. exec "$#" commands can't get my bash command. When I write there manually bash like exec "bash", it works.. On the other hand, it can't.
My steps are same in the best practices of docker.
What is my mistake?
As I see, the arguments lose when run another script in entrypoint.sh
So before run it, assign the arguments to a variable.
#!/bin/bash
# to save arguments assign them a variable before running script below
COM=$#
# Load the script of environment variables
. /opt/intel/openvino/bin/setupvars.sh
# export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH
# export PYTHONPATH=/usr/local/lib/python3.6/dist-packages/:$PYTHONPATH
echo "INITIALIZZEE"
printenv
# Run the main container command
exec $COM

env variables not substituted in docker file

FROM alpine:3.11
COPY out/ /bin/
CMD ["command", "--flag1", "${HOST}", "--flag2", "${PORT}", "--flag3", "${AUTH_TOKEN}"]
This is the docker file used. I am loading the env variables during run through an env file.
But the variables are not substituted when running the command. If I override the CMD and exec into the container I am able to see the envs though.
What am I missing here?
You are running CMD in exec mode. Switch to shell mode and it will work out. As for the environment variables to be present you need a shell. more reading
your example:
CMD command --flag1 ${HOST} --flag2 ${PORT} --flag3 ${AUTH_TOKEN}
Full generic example:
Dockerfile:
FROM debian:stretch-slim
CMD echo ${env}
Run:
docker build .
docker run --rm -e env=hi <image id from build step>
hi

Dockerfile placeholders not replaced during build

Using Docker for Windows, Community version 17.06.0-ce-win19 (12801), Windows 10
Dockerfile
FROM frolvlad/alpine-oraclejdk8:slim
ARG APP_NAME=client-default
RUN mkdir -p /client/
ADD build/libs/$APP_NAME.jar /client/$APP_NAME.jar
ENTRYPOINT ["java", "-jar", "/client/$APP_NAME.jar"]
Running
docker build --build-arg APP_NAME=client-1 -t test/client-1 .
Placeholders not replaced after build (tried ${APP_NAME}, %APP_NAME%)
ARG and ENV are not replaced by Docker in an ENTRYPOINT or a CMD when you use the EXEC form inside []. If the ENTRYPOINT or CMD use the shell form (string, not an array) the shell will be able to do the variable substitution for you.
ARG and ENV variables will be available in RUN commands in the container as environment variables.
Docker will also replace $VARIABLES in the Dockerfile in the following instructions:
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
Only ENV variables will become available in a CMD or ENTRYPOINT, only in the environment of the running container:
Using ARG
FROM frolvlad/alpine-oraclejdk8:slim
ARG APP_NAME=client-default
ENV APP_NAME=$APP_NAME
RUN mkdir -p /client/
ADD build/libs/$APP_NAME.jar /client/$APP_NAME.jar
ENTRYPOINT ["sh", "-c", "java -jar /client/$APP_NAME.jar"]
or with only ENV
FROM frolvlad/alpine-oraclejdk8:slim
ENV APP_NAME=client-default
RUN mkdir -p /client/
ADD build/libs/$APP_NAME.jar /client/$APP_NAME.jar
ENTRYPOINT ["sh", "-c", "java -jar /client/$APP_NAME.jar"]
Environment variables in Dockerfile are declared with the ENV statement.
In your case:
FROM frolvlad/alpine-oraclejdk8:slim
ENV APP_NAME client-default
RUN mkdir -p /client/
ADD build/libs/${APP_NAME}.jar /client/${APP_NAME}.jar
ENTRYPOINT ["java", "-jar", "/client/${APP_NAME}.jar"]
ARG is only available during the build of a Docker image (RUN etc), not after the image is created and containers are started from it (ENTRYPOINT OR CMD)
ARG variable doesn't get substituted in Dockerfile. ARG IS really an environment variable. During the build, docker "runs" the container using ARG variables as environment variables. Easy to prove (you can test it): Try RUN printenv on your Dockerfile and you'll see the ARG as environment variable
So, you've got two problems:
First:
The Steps in console DOESN'T substitute the ARG variable. So, your jar is really in the container, your line:
ADD build/libs/$APP_NAME.jar /client/$APP_NAME.jar
it's working. Try it.
Second:
Entrypoint is for the running container, not for the image, so you need to put your ARG into an ENV variable, and not to use the exec form. If your use the exec form of ENTRYPOINT does not invoke a command shell. This means that normal shell processing does not happen. If you want shell processing then either use the shell form or execute a shell directly, for example:
ENTRYPOINT cat ${APP_NAME_RUN}
So, try this working example:
FROM ubuntu
ARG APP_NAME=client
RUN mkdir -p /client/
ADD ./$APP_NAME.txt /client/$APP_NAME.txt
ENV APP_NAME_RUN="/client/${APP_NAME}.txt"
RUN echo $APP_NAME_RUN
ENTRYPOINT cat ${APP_NAME_RUN}

Resources