Can docker entrypoint in shell form use runtime command args?

7/16/2020

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!

-- Jee Force
bash
docker
kubernetes
linux
shell

1 Answer

7/17/2020

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:

  1. If either the ENTRYPOINT or CMD (or both) is the shell form, it's wrapped in ["/bin/sh", "-c", "..."].
  2. The 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:

<!-- language: lang-sh -->
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.

-- David Maze
Source: StackOverflow