ENTRYPOINT vs CMD: back to basics
- Transfer

The name ENTRYPOINTalways confused me. This name implies that each container must have a specific instruction ENTRYPOINT. But after reading the official documentation, I realized that this is not true.
Fact 1: You need to define at least one instruction ( ENTRYPOINTor CMD) (to run).
If you do not define any of them, you will receive an error message. Let's try to launch an Alpine Linux image for which neither ENTRYPOINT, nor are defined CMD.
$ docker run alpine
docker: Error response from daemon: No command specified.
See 'docker run --help'.Fact 2: If only one of the instructions is defined at run time, then CMDthey ENTRYPOINTwill have the same effect.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ls /usr $ docker build -t test .$ docker run test
bin
lib
local
sbin
share We will get the same results if we use CMDinstead ENTRYPOINT.
$ cat Dockerfile
FROM alpine
CMD ls /usr # Using CMD instead $ docker build -t test .$ docker run test
bin
lib
local
sbin
share Although this example shows that there is no difference between ENTRYPOINTand CMD, it can be seen by comparing the metadata of the containers.
For example, the first Dockerfile (with a specific one ENTRYPOINT):
$ docker inspect b52 | jq .[0].Config
{
...
"Cmd": null,
...
"Entrypoint": [
"/bin/sh",
"-c",
"ls /"
],
...
}Fact 3: Both for CMDand for ENTRYPOINTthere are shell and exec modes .
From the manual :
ENTRYPOINThas two execution modes:
ENTRYPOINT ["executable", "param1", "param2"](executable form, preferably)ENTRYPOINT command param1 param2(shell shape)
So far, we have used shell mode , or shell mode . This means that our team ls -lruns inside /bin/sh -c. Let's try both modes and examine the running processes.
Shell mode :
$ cat Dockerfile
FROM alpine
ENTRYPOINT ping www.google.com # "shell" format $ docker build -t test .$ docker run -d test
11718250a9a24331fda9a782788ba315322fa879db311e7f8fbbd9905068f701 Then we will study the processes:
$ docker exec 117 ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh -c ping www.google.com
7 root 0:00 ping www.google.com
8 root 0:00 psNotice that the process sh -chas a PID of 1. Now the same thing, using the "exec" mode :
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ping", "www.google.com"] # "exec" format $ docker build -t test .$ docker run -d test
1398bb37bb533f690402e47f84e43938897cbc69253ed86f0eadb6aee76db20d $ docker exec 139 ps
PID USER TIME COMMAND
1 root 0:00 ping www.google.com
7 root 0:00 psWe see that when using exec mode, the command ping www.google.comworks with the PID process identifier equal to 1, and the process is sh -cabsent. Keep in mind that the above example works exactly the same if used CMDinstead ENTRYPOINT.
Fact 4: exec mode is recommended.
This is because containers are designed to contain a single process. For example, the signals sent to the container are redirected to the process running inside the container with the PID equal to 1. Very informative experience: to check the redirection, it is useful to start the ping container and try to press ctrl + c to stop the container.
A container defined using exec mode exits successfully:
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ping", "www.google.com"] $ docker build -t test .$ docker run test
PING www.google.com (172.217.7.164): 56 data bytes
64 bytes from 172.217.7.164: seq=0 ttl=37 time=0.246 ms
64 bytes from 172.217.7.164: seq=1 ttl=37 time=0.467 ms
^C
--- www.google.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.246/0.344/0.467 ms
$When using shell mode , the container does not work as expected.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ping www.google.com $ docker build -t test .$ docker run test
PING www.google.com (172.217.7.164): 56 data bytes
64 bytes from 172.217.7.164: seq=0 ttl=37 time=0.124 ms
^C^C^C^C64 bytes from 172.217.7.164: seq=4 ttl=37 time=0.334 ms
64 bytes from 172.217.7.164: seq=5 ttl=37 time=0.400 ms Help, I can’t get out! The signal SIGINTthat was directed to the process shwill not be redirected to the subprocess ping, and the shell will not shut down. If for some reason you really want to use shell mode , the way out of this situation is to use a execshell process to replace the process ping.
$ cat Dockerfile
FROM alpine
ENTRYPOINT exec ping www.google.com Fact 5: no shell? There are no environment variables.
The problem of starting NOT in shell mode is that you cannot take advantage of environment variables (such as $PATH) and other features that the shell provides. There are two problems in the Dockerfile below :
$ cat Dockerfile
FROM openjdk:8-jdk-alpine
WORKDIR /data
COPY *.jar /data
CMD ["java", "-jar", "*.jar"] # "exec" format First problem: since you cannot use the environment variable $PATH, you need to specify the exact location of the java executable. The second problem: wildcard characters are interpreted by the shell itself, so the string *.jarwill not be processed correctly. After fixing these issues, the resulting Dockerfile looks like this:
FROM openjdk:8-jdk-alpine
WORKDIR /data
COPY *.jar /data
CMD ["/usr/bin/java", "-jar", "spring.jar"] Fact 6: CMD arguments are appended to the end of the statement ENTRYPOINT... sometimes.
This is where the confusion begins. The manual has a table whose purpose is to clarify this issue.

I'll try to explain on the fingers .
Fact 6a: If you use shell mode for ENTRYPOINT, it is CMDignored.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ls /usr
CMD blah blah blah blah $ docker build -t test .$ docker run test
bin
lib
local
sbin
share The string blah blah blah blahwas ignored.
FACT 6b: When using the exec for ENTRYPOINTthe arguments CMDare added at the end.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ls", "/usr"]
CMD ["/var"] $ docker build -t test .$ docker run test
/usr:
bin
lib
local
sbin
share
/var:
cache
empty
lib
local
lock
log
opt
run
spool
tmp The argument /varwas added to our instructions ENTRYPOINT, which allowed us to efficiently run the command ls/usr/var.
Fact 6c: When using exec mode for an instruction, ENTRYPOINTyou must use exec mode for the instruction as well CMD. If this is not done, Docker will try to add sh -cto the already added arguments, which may lead to some unpredictable results.
Fact 7: Instructions ENTRYPOINTand CMDcan be overridden using command line flags.
A flag --entrypointcan be used to override an instruction ENTRYPOINT:
docker run --entrypoint [my_entrypoint] test Everything that follows the name of the image in the command docker runoverrides the instruction CMD:
docker run test [command 1] [arg1] [arg2] All of the above facts are true, but keep in mind that developers have the ability to redefine flags in a team docker run. It follows that ...
Enough facts ... What should I do?
Ok, if you read the article to this place, then here is the information in which cases to use ENTRYPOINT, and in which CMD.
This decision I am going to leave to the discretion of the person creating the Dockerfile , which can be used by other developers.
Use ENTRYPOINTif you do not want developers to modify the executable that runs when the container starts. You can imagine that your container is an executable shell . A good strategy would be to define a stable combination of parameters and executable file as ENTRYPOINT. For it, you can (optionally) specify CMDdefault arguments that other developers can override.
$ cat Dockerfile
FROM alpine
ENTRYPOINT ["ping"]
CMD ["www.google.com"] $ docker build -t test .Startup with default settings :
$ docker run test
PING www.google.com (172.217.7.164): 56 data bytes
64 bytes from 172.217.7.164: seq=0 ttl=37 time=0.306 msOverride CMD with your own parameters:
$ docker run test www.yahoo.com
PING www.yahoo.com (98.139.183.24): 56 data bytes
64 bytes from 98.139.183.24: seq=0 ttl=37 time=0.590 ms Use only CMD(without definition ENTRYPOINT) if you want developers to easily override the executable. If an entry point is defined, the executable file can still be redefined using the flag --entrypoint. But for developers it will be much more convenient to add the desired command at the end of the line docker run.
$ cat Dockerfile
FROM alpine
CMD ["ping", "www.google.com"] $ docker build -t test .Ping is good, but let's try to launch a container with a shell instead of a command ping.
$ docker run -it test sh
/ # ps
PID USER TIME COMMAND
1 root 0:00 sh
7 root 0:00 ps
/ # I prefer this method for the most part, because it gives developers the freedom to easily override the executable with a shell or other executable.
Cleaning
After running the commands, a bunch of stopped containers remained on the host. Clean them with the following command:
$ docker system pruneFeedback
I will be glad to hear your thoughts on this article in the comments below. In addition, if you know an easier way to search for a docker using jq so that you can do something like docker inspect [id] | jq * .config, write in the comments too.
John Zakkone
Captain Docker and cloud engineer at IBM. Specializes in Agile, microservices, containers, automation, REST, DevOps.