Docker uses a combination of 'Endpoint' and 'Command' values to compose the command it uses to launch images. Exactly which values are used depends on which are set and their format.
The documentation gives all the necessary information, but it is spread across a number of entries.
In this post, I'll try to reduce the bewildering list of possible permutations (see below) to a couple of simple choices.
I ran these tests on Docker version 20.10.12 on Ubuntu, but as far as I know, this behaviour is pretty stable.
Running a Docker Image
When Docker launches images, it starts a single process via a single command.
If I run an Alpine Linux image without any extra arguments, this is what happens:
$ docker run -ti alpine:3.17
/ #
Docker has run /bin/sh
thanks to the CMD
instruction in the Alpine Linux Dockerfile.
If we append the ls
command this is what we get:
$ docker run -ti alpine:3.17 ls
bin etc lib mnt proc run srv tmp var
dev home media opt root sbin sys usr
Here, ls
is the "run argument". It has taken precedence over /bin/sh
specified in the Dockerfile.
Knowing exactly what command and arguments get executed depends on a combination of what's in the Dockerfile (and, recursively, the Dockerfiles that it is based on) plus the command line arguments passed to docker run
.
By the way, we can also confirm the idea that the command Docker executes is the only process that is started:
$ docker run -ti alpine:latest ps afux
PID USER TIME COMMAND
1 root 0:00 ps afux
So Docker has run my ps
command as PID 1
- the only process running in the container.
Dockerfiles
Dockerfiles can contain the ENTRYPOINT
instruction and the CMD instruction.
Only one CMD
or ENTRYPOINT
should be included per Dockerfile.
If more than one is present, only the last will be used.
Base Image Dockerfiles
If a Dockerfile uses a base image (via FROM
), it inherits that image's ENTRYPOINT
and CMD
.
Most popular base images do not set ENTRYPOINT
.
Here's a list:
An exception to this is Redis, which launches a script called docker-entrypoint.sh, which does some setup and then executes whatever was passed to CMD.
So, in the most common case, when we need to override stuff, it's only the CMD
instruction that will already have been set, or, if ENTRYPOINT
is set, there will be little need to change it.
Formats
ENTRYPOINT
and CMD
have two formats, shell
and JSON
.
As we will see below, the format that is used changes the rules about how the final command line is composed.
shell Format
When a simple text is supplied, it is called 'shell' format:
ENTRYPOINT echo Ciao
CMD echo Ciao
JSON Array format
If a well formed JSON array is supplied, it is called the 'exec' form or 'array' form:
ENTRYPOINT ["echo", "Ciao"]
CMD ["echo", "Ciao"]
N.B. Well-formed JSON Array is required - quotes must be "
.
If single '
quotes are used, the value is treated as a shell format.
docker run
Options and Arguments
--entrypoint
If the --entrypoint
parameter is passed to docker run
, the value overrides any ENDPOINT
instruction that has been specified in a Dockerfile. Note that is also clears any Command that has been supplied.
You can't use the 'JSON Array' format when specifying --entrypoint
, the value you pass is always interpreted as being in 'shell' format.
Passing an empty string to --entrypoint
is a handy way of nullifying any ENTRYPOINT
(and CMD
) that may have been set, in order to fall back to using docker run
arguments exclusively.
If we don't specify arguments, we get an error:
$ docker run -ti --entrypoint="" busybox:latest
docker: Error response from daemon: No command specified.
Arguments
Any arguments passed to docker run
after the image name override any CMD
instruction.
How the Entrypoint and Command Interact
Based on the above, from now on, I will only refer to the Entrypoint and Command, however they have been indicated.
If an Entrypoint is supplied in shell format, any Command is ignored.
If, on the other hand, there is a JSON format Entrypoint, any JSON format Command is appended.
But, if the Command is in shell format, /bin/sh -c
is prepended to the Command.
I think this last case, of all the combinations available, is the most surprising to the user, and only makes sense in cases where the Entrypoint is a setup script, as in the case of the Redis image above.
Running Directly
When a JSON Entrypoint is supplied, the command line which Docker builds from any Entrypoint and Command is executed directly, not via /bin/sh
.
As the final command is not run via a shell, this format does not do variable substitution. This is a point to remember - and often causes confusion. A subtle change, from shell format to JSON format, can cause errors when starting a container.
Running commands directly fails if the first array entry is not in the image's $PATH
and is not a full path to an executable.
Running a Shell
When the 'shell' format is used, the executable and arguments obtained from combining any Entrypoint and Command are passed to a command shell, e.g. /bin/sh -c
.
Running commands via sh
fails if the first array entry is not in the shell's $PATH
and is not a full path to an executable.
Permutations of Entrypoint and Command
Below is a list of the permutations of Entrypoint and Command, indicating how Docker interprets them.
'JSON' refers to the 'exec'/'array' JSON format.
Entrypoint type | Command type | Command used? | Outcome |
---|---|---|---|
- | - | n | Uses base image's `ENTRYPOINT`. If there is none, tries to run '/bin/sh'. |
- | shell | y | Executes Command via `sh`. |
- | JSON | y | Executes Command command directly. |
shell | - | n | Passes Entrypoint command to `sh`. |
shell | shell | n | Passes Entrypoint command to `sh`, **ignoring** Command. |
shell | JSON | n | Passes Entrypoint command to `sh`, **ignoring** Command. |
JSON | - | n | Executes Entrypoint directly. |
JSON | shell | y | Executes Entrypoint, appending '/bin/sh -c', then Command |
JSON | JSON | y | Executes Entrypoint directly, appending Command |
What Should I Use?
After trawling through all this, my conclusion is as follows:
in general, avoid using an Entrypoint,
only use an Entrypoint if you need to do setup before running the container,
if necessary, use
CMD
in your Dockerfiles to override what is set in your base image, and pass arguments todocker run
if your ownCMD
needs to be overridden,use 'shell' format for
CMD
if you want shell pre-processing (e.g. environment variable substitution), otherwise, use the 'JSON' format,pass
--entrypoint=""
plus a Command to Dockerrun
if your base image has anENTRYPOINT
instruction that you don't want to run.