Here is a sample dockerfile with a shell-form entrypoint:
FROM ubuntu
ENTRYPOINT /bin/echo "(shell) ENTRYPOINT@image ($0)"
Below, are some of the outputs that I see from various runs:
$ docker run -it sc-test:v4
(shell) ENTRYPOINT@image (/bin/sh)
$ docker run -it sc-test:v4 /bin/echo
(shell) ENTRYPOINT@image (/bin/echo)
$ docker run -it sc-test:v4 /bin/cat
(shell) ENTRYPOINT@image (/bin/cat)
$ docker run -it sc-test:v4 /bin/dog
(shell) ENTRYPOINT@image (/bin/dog)
$ docker run -it sc-test:v4 "/bin/dog ($0)"
(shell) ENTRYPOINT@image (/bin/dog (-bash))
Based on docker documentation here, we can see that the command args are ignored.
However, the value of $0
changes with the args provided. Can someone explain why this happens? Thanks!
The table in that part of the Docker documentation isn't technically correct: Docker doesn't actually drop or ignore the command part when there's a shell-form entrypoint. What actually happens here (and your example demonstrates) is:
ENTRYPOINT
or CMD
(or both) is the shell form, it's wrapped in ["/bin/sh", "-c", "..."]
.ENTRYPOINT
and CMD
lists are concatenated to form a single command list.Let's take your third example. This is, in Dockerfile syntax,
ENTRYPOINT /bin/echo "(shell) ENTRYPOINT@image ($0)"
CMD ["/bin/cat"]
and the resulting combined command is (in JSON array syntax, expanded for clarity)
<!-- language: json -->[
"/bin/sh",
"-c",
"/bin/echo \"(shell) ENTRYPOINT@image ($0)\"",
"/bin/cat"
]
So, what does sh -c
do if you give it multiple arguments? The POSIX spec for the sh
command documents the syntax as
sh -c command_string [command_name [argument...]]
and further documents
-c: Read commands from the command_string operand. Set the value of special parameter 0 ... from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. No commands shall be read from the standard input.
That's what you're seeing in your examples. If ENTRYPOINT
is a bare string and CMD
is a JSON array, then within the ENTRYPOINT
string command, the arguments in CMD
can be used as $0
, $1
, and so on. If both are bare strings, both get wrapped in sh -c
, and you'll get something like:
ENTRYPOINT /bin/echo "$0 is /bin/sh, $1 is -c, and then $2"
CMD the rest of the line
In your first example, the command part is empty, and in this case (still from the POSIX sh
documentation)
If command_name is not specified, special parameter 0 shall be set to ... normally a pathname used to execute the sh utility.
Your last example is slightly more subtle:
<!-- language: lang-sh -->docker run -it sc-test:v4 "/bin/dog ($0)"
Since the string is double-quoted, your local shell expands the $0
reference in it, which is how bash
gets in there; then since it's a single (quoted) word, it becomes the single command_name argument to sh -c
.
There's two more normal patterns for using ENTRYPOINT
and CMD
together. The pattern I prefer has CMD
be a full shell command, and ENTRYPOINT
does some first-time setup and then runs a command like exec "$@"
to run that command. There's also a "container as command" pattern where ENTRYPOINT
has a complete command (perhaps with involved JVM arguments) and CMD
additional options. In these cases the ENTRYPOINT
must be JSON-array syntax:
ENTRYPOINT ["/script-that-exec-dollar-at.sh"]
CMD ["the_command", "--option"]
Since the ENTRYPOINT
string doesn't directly reference $0
, $1
, etc. the CMD
arguments are effectively ignored by the sh -c
wrapper. If you had ENTRYPOINT script.sh
it would be invoked by sh -c
as a subprocess with no arguments, and the CMD
would get lost.
It's probably clearer for the Docker documentation to say "if ENTRYPOINT
is a string then CMD
is ignored" than to try to explain the subtleties of this.