I am looking at Docker's documentation to understand what would be behavior of ENTRYPOINT defined in exec form and CMD defined in shell form.
The example in the docs only shows something like exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd which does not tell me anything.
For example, what if we had:
ENV JAVA_OPTS '-XX:+UseG1GC -Xms512m -Xmx1536m'
ENTRYPOINT ["java"]
CMD $JAVA_OPTS -jar app.jar
Would the problem with signal propagation exist here (in other words, would any extra subshell be spawned here)?
If either ENTRYPOINT or CMD are not JSON arrays, they are interpreted as strings and converted to a length-3 array ["/bin/sh", "-c", "..."].
The resulting two lists are concatenated.
So in your example, the final command list would be
["java", "/bin/sh", "-c", "$JAVA_OPTS -jar app.jar"]
or in Bourne shell syntax
java /bin/sh -c '$JAVA_OPTS -jar app.jar'
This passes the shell interpreter /bin/sh as an argument to java; that almost certainly is not what you intend.
If the CMD is anything other than a complete command, it must use the JSON-array syntax, which in turn means it can't use any shell features and it can't expand environment variable references. This would include both the "container-as-command" pattern where ENTRYPOINT is the command to run and CMD its arguments, and the antipattern you show here where ENTRYPOINT is the interpreter only (and you have to repeat the -jar app.jar option in a docker run command override).
I prefer a setup where CMD is always a complete shell command. If you have an ENTRYPOINT at all, it's a script that does some startup-time setup and then runs exec "$#" to run the command passed as arguments. This can accept either form of CMD.
# ENTRYPOINT ["./docker-entrypoint.sh"] # optional
CMD java $JAVA_OPTS -jar app.jar # in a single shell-format CMD
It took me a while but I finally figured out what is the point of /bin/sh -c in this use-case. We can for example use tini as an entrypoint
ENTRYPOINT ["tini", "--"]
and then a shell for of CMD, but we need to use exec in order to replace a subshell, that is
CMD exec java $JAVA_OPTS -jar app.jar
Related
I need to pass optional, runtime parameter to a command in Docker.
The idea is that if PARAM env variable is set when docker is being run - it should be passed to java command as --key VALUE , and when runtime parameter is not set - it shoulddn't pass anything - in particular it shouldn't pass --key parameter name.
I.e. it should run following command if PARAM env variable is set:
/bin/java -jar artifact.jar --key $PARAM
And following if it's not:
/bin/java -jar artifact.jar
I wanted to use :+ syntax, but it's resolved during build time, which means it won't be affected by runtime env variable.
docker build -t test-abc . && docker run -e "PARAM=oooo" test-abc
FROM openjdk:17
ENV PARAM=${PARAM:+"--key $PARAM"}
ENTRYPOINT /bin/java -jar artifact.jar $PARAM
Prepare a script that will correctly handle arguments:
#!/bin/bash
args=()
if [[ -v PARAM ]]; then
args=(--key "$PARAM")
fi
/bin/java -jar artifact.jar "${args[#]}" "$#"
And add it:
ADD entrypoint.sh /entrypoint.sh
CMD chmod +x /entrypoint.sh
ENTRYPOINT /entrypoint.sh
If you are using ENTRYPOINT here, and you rewrite it in JSON-array syntax, then you can pass additional arguments to your program after the docker run image-name. (Technically they replace the CMD.)
# Dockerfile: do use JSON-array syntax; do not attempt to handle options;
# must use ENTRYPOINT for "container-as-command" layout
ENTRYPOINT ["java", "-jar", "artifact.jar"]
docker run test-abc --key oooo
You can also write the parameter expansion directly inside the command string. This requires you to use shell-syntax ENTRYPOINT or CMD, and so will essentially forbid you from providing extra options you don't set up this way. The ${VARIABLE:+value} syntax is supported in the POSIX specification so this should work with all Linux distribution base images, even if they don't include bash.
ENTRYPOINT java -jar artifact.jar ${PARAM:=--key "$PARAM"}
BashFAQ/050 has some more syntax suggestions, some of which are bash-specific, but the more detailed ones do require a good understanding of shell splitting and expansions.
I am running Jupyter in a Docker container. The following shell form will run fine:
CMD jupyter lab --ip='0.0.0.0' --port=8888 --no-browser --allow-root /home/notebooks
But the following one on docker file will not:
ENTRYPOINT ["/bin/sh", "-c"]
CMD ["jupyter", "lab", "--ip='0.0.0.0'", "--port=8888", "--no-browser", "--allow-root", "/home/notebooks"]
The error is:
usage: jupyter [-h] [--version] [--config-dir] [--data-dir] [--runtime-dir] [--paths] [--json] [subcommand]
jupyter: error: one of the arguments --version subcommand --config-dir --data-dir --runtime-dir --paths is required
So obviously /bin/sh -c sees the jupyter argument, but not the following ones.
Interestingly,
CMD ["jupyter", "lab", "--ip='0.0.0.0'", "--port=8888", "--no-browser", "--allow-root", "/home/notebooks"]
will run fine, so it cannot be the number of arguments, or can it?
According to https://docs.docker.com/engine/reference/builder/#cmd, the shell form of CMD executes with /bin/sh -c. So from my point of view I see little difference in the 2 versions. But the reason must be how the exec forms are being evaluated when ENTRYPOINT and CMD are present at the same time.
At a very low level, Linux commands are executed as a series of "words". Typically your shell will take a command line like ls -l "a directory" and breaks that into three words ls -l a directory. (Note the space in "a directory": in the shell form that needs to be quoted to be in the same word.)
The Dockerfile CMD and ENTRYPOINT (and RUN) commands have two forms. In the form you've specified that looks like a JSON array, you are explicitly specifying how the words get broken up. If it doesn't look like a JSON array then the whole thing is taken as a single string, and wrapped in an sh -c command.
# Explicitly spelling out the words
RUN ["ls", "-l", "a directory"]
# Asking Docker to run it via a shell
RUN ls -l 'a directory'
# The same as
RUN ["sh", "-c", "ls -l 'a directory'"]
If you specify both ENTRYPOINT and CMD the two lists of words just get combined together. The important thing for your example is that sh -c takes the single next word and runs it as a shell command; any remaining words can be used as $0, $1, ... positional arguments within that command string.
So in your example, the final thing that gets run is more or less
ENTRYPOINT+CMD ["sh", "-c", "jupyter", ...]
# If the string "jupyter" contained "$1" it would expand to the --ip option
The other important corollary to this is that, practically, ENTRYPOINT can't be the bare-string format: when the CMD is appended to it you get
ENTRYPOINT some command
CMD with args
ENTRYPOINT+CMD ["sh", "-c", "some command", "sh", "-c", "with args"]
and by the same rule all of the CMD words get ignored.
In practice you almost never need to explicitly put sh -c or a SHELL declaration in a Dockerfile; use a string-form command instead, or put complex logic into a shell script.
I am trying to deploy an app in payara micro based on payara dockerimage and I need to pass one arguement snapshotversion in ENTRYPOINT(basically i want to access the build args in ENTRYFORM) exec form, as exec form of ENTRYPOINT is preferred: my docker file is as follows:
FROM payara/micro:5.193.1
ARG snapshotversion
ENV snapshotvs=$snapshotversion
RUN jar xf payara-micro.jar
COPY /service/war/target/app-emailverification-service-war-${snapshotversion}.war ${DEPLOY_DIR}/
COPY ojdbc6.jar ${PAYARA_HOME}/
COPY --chown=payara domain.xml /opt/payara/MICRO-INF/domain/domain.xml
RUN cd /opt/payara/MICRO-INF/domain && ls -lrt
#ENTRYPOINT ["java", "-jar", "/opt/payara/payara-micro.jar", "--deploy", "/opt/payara/deployments/app-service-war-$snapshotvs.war", "--domainConfig", "/opt/payara/MICRO-INF/domain/domain.xml","--addLibs", "/opt/payara/ojdbc6.jar"]
ENTRYPOINT java -jar /opt/payara/payara-micro.jar --deploy /opt/payara/deployments/app-service-war-$snapshotvs.war --domainConfig /opt/payara/MICRO-INF/domain/domain.xml --addLibs /opt/payara/ojdbc6.jar
The commented ENTRYPOINT does not work. Container logs says invalid deployment. What am i missing here? Also how can I use CMD with this. Can someone post an example.
The commented line doesn't work, because it is an exec form of ENTRYPOINT, which doesn't invoke shell (/bin/sh -c), so variable substitution doesn't happening.
If you want to use an exec form and environment variables you need to specify it directly:
ENTRYPOINT ["sh", "-c", "your command with env variable"]
To your question about how can you use CMD with this, for example like this:
ENTRYPOINT ["sh", "-c"]
CMD ["your command with env variable"]
You mentioned, that you want to use build args in ENTRYPOINT instruction. It's not really possible, because nor ARG nor ENV are expanded in ENTRYPOINT or CMD: https://docs.docker.com/engine/reference/builder/#environment-replacement, https://docs.docker.com/engine/reference/builder/#scope
Also you could take a look at great page with best practices for writing Dockerfile and ENTRYPOINT instructions specifically.
Two suggestions that complement each other:
If you're COPYing a file into the image, you can give it a fixed name inside the image. That avoids this problem.
WORKDIR /opt/payara
COPY service/war/target/app-emailverification-service-war-${snapshotversion}.war deployments/app-service.war
If you have a particularly long or involved command that you're trying to make be the main container process, wrap it in a shell script. You want to make sure to exec the main container process to avoid some trouble around signal handling (resulting in docker stop pausing for 10 seconds and then hard-killing your actual process).
#!/bin/sh
exec java \
-jar /opt/payara/payara-micro.jar \
--deploy /opt/payara/deployments/app-service.war \
--domainConfig /opt/payara/MICRO-INF/domain/domain.xml \
--addLibs /opt/payara/ojdbc6.jar
COPY launch.sh ./
RUN chmod +x launch.sh
CMD ["/opt/payara/launch.sh"]
In this second case, it's a shell script, so you can have ordinary shell variable substitutions.
Here's a simple Dockerfile
FROM centos:6.6
ENTRYPOINT ["/bin/bash", "-l", "-c"]
CMD ["echo", "foo"]
Unfortunately it doesn't work. Nothing is echo'd when you run the resulting container that's built.
If you comment out the ENTRYPOINT then it works. However, if you set the ENTRYPOINT to /bin/sh -c, then it fails again
FROM centos:6.6
ENTRYPOINT ["/bin/sh", "-c"]
CMD ["echo", "foo"]
I thought that was the default ENTRYPOINT for an container that didn't have one defined, why didn't that work?
Finally, this also works
FROM centos:6.6
ENTRYPOINT ["/bin/bash", "-l", "-c"]
CMD ["echo foo"]
Before I submit an issue, I wanted to see if I'm doing something obviously wrong?
I'm using rvm inside my container which sort of needs a login shell to work right.
Note that the default entry point/cmd for an official centos 6 image is:
no entrypoint
only CMD ["/bin/bash"]
If you are using the -c command, you need to pass one argument (which is the full command): "echo foo".
Not a series of arguments (CMD ["echo", "foo"]).
As stated in dockerfile CMD section:
If you use the shell form of the CMD, then the <command> will execute in /bin/sh -c:
FROM ubuntu
CMD echo "This is a test." | wc -
If you want to run your <command> without a shell then you must express the command as a JSON array and give the full path to the executable
Since echo is a built-in command in the bash and C shells, the shell form here is preferable.
Is there a way to execute a command as an argument in a Dockerfile ENTRYPOINT? I am creating an image that should automatically run mpirun for the number of processors, i.e., mpirun -np $(nproc) or mpirun -np $(getconf _NPROCESSORS_ONLN).
The following line works:
ENTRYPOINT ["/tini", "--", "mpirun", "-np", "4"] # works
But I cannot get an adaptive form to work:
ENTRYPOINT ["/tini", "--", "mpirun", "-np", "$(nproc)"] # doesn't work
ENTRYPOINT ["/tini", "--", "mpirun", "-np", "$(getconf _NPROCESSORS_ONLN)"] # doesn't work
Using the backtick `nproc` notation does not work either. Nor can I pass an environment variable to the command.
ENV processors 4
ENTRYPOINT ["/tini", "--", "mpirun", "-np", "$processors"] # doesn't work
Has anyone managed to get this kind of workflow?
Those likely won't work: see issue 4783
ENTRYPOINT and CMD are special, as they get started without a shell (so you can choose your own) and iirc they are escaped too.
Unlike the shell form, the exec form does not invoke a command shell.
This means that normal shell processing does not happen.
For example, ENTRYPOINT [ "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: ENTRYPOINT [ "sh", "-c", "echo", "$HOME" ].
A workaround would be to use a script.
COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
That script, when docker run triggers it, should at least benefit from the environment variable.
See for example the Dockerfile of vromero/activemq-artemis-docker, which runs the script docker-entrypoint.sh.
In order to allow CMD to run as well, the scripts end with:
exec "$#"
(It will execute whatever parameter comes after, either from the CMD directive, or from docker run parameters)
The OP Gilly adds in the comments:
I use in the Dockerfile:
COPY docker-entrypoint.sh
ENTRYPOINT ["/tini", "--", "/docker-entrypoint.sh"]
And in the entrypoint script:
#!/bin/bash
exec mpirun -np $(nproc) "$#"
It is because you are using the exec form for your entry point and variable substitution will not happen in the exec form.
This is the exec form:
ENTRYPOINT ["executable", "param1", "param2"]
this is the shell form:
ENTRYPOINT command param1 param2
From the official documentation:
Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen. For example, ENTRYPOINT [ "echo", "$HOME" ] will not do variable substitution on $HOME