📜 ⬆️ ⬇️

ENTRYPOINT vs CMD: Back to Basics

Construction


The name ENTRYPOINT always confused me. This name implies that each container must have a specific ENTRYPOINT . But after reading the official documentation, I realized that this was not true.


Fact 1: ENTRYPOINT define at least one instruction ( ENTRYPOINT or CMD ) (to run).


If you do not identify any of them, you will receive an error message. Let's try launching an Alpine Linux image for which neither ENTRYPOINT nor CMD are defined.


 $ docker run alpine docker: Error response from daemon: No command specified. See 'docker run --help'. 

Fact 2: If only one of the instructions is specified at runtime, then both CMD and ENTRYPOINT will 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 CMD instead of 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 ENTRYPOINT and CMD , you can see it by comparing container metadata.


For example, the first Dockerfile file (with a specific ENTRYPOINT ):


 $ docker inspect b52 | jq .[0].Config { ... "Cmd": null, ... "Entrypoint": [ "/bin/sh", "-c", "ls /" ], ... } 

Fact 3: Both for CMD and ENTRYPOINT there are shell and exec modes.


From the manual :


ENTRYPOINT has two execution modes:
  • ENTRYPOINT ["executable", "param1", "param2"] (executable form, preferred)
  • ENTRYPOINT command param1 param2 (shell form)

So far, we have used shell mode, or shells. This means that our ls -l runs 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 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 ps 

Note that the sh -c process has a PID of 1. Now the same, 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 ps 

We see that when using the exec mode, the ping www.google.com command works with the process PID of 1, and the sh -c process is missing. Note that the above example works the same way if you use CMD instead of ENTRYPOINT .


Fact 4: The exec mode is recommended.


This is due to the fact that containers are designed to contain a single process. For example, signals sent to a container are redirected to a process running inside a container with a PID of 1. Very informative experience: to check the fact of redirection, it is useful to start the ping container and try pressing ctrl + c to stop the container.


The container defined using the exec mode successfully exits:


 $ 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 the 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 not go out! The SIGINT signal that was sent to the sh process will not be redirected to the ping subprocess, and the shell will not shut down. If for some reason you really want to use shell mode, the way out is to use exec to replace the shell process with the ping process.


 $ cat Dockerfile FROM alpine ENTRYPOINT exec ping www.google.com 

Fact 5: No shell? No environment variables.


The problem with starting NOT in shell mode is that you cannot take advantage of the environment variables (such as $PATH ) and other features that the use of 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 

The first problem: since you cannot use the $PATH environment variable, you need to specify the exact location of the java-executable file. The second problem: wildcards are interpreted by the shell itself, so the string *.jar will not be processed correctly. After fixing these problems, the resulting Dockerfile file 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 attached to the end of the ENTRYPOINT ... sometimes.


This is where the confusion begins. The manual has a table whose purpose is to clarify this issue.


Entrypointscreen


I will try to explain on the fingers .


Fact 6a: If you use shell mode for ENTRYPOINT , the CMD ignored.


 $ 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 blah was ignored.


FACT 6b: When using exec for ENTRYPOINT CMD arguments are 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 /var argument was added to our ENTRYPOINT , which allowed us to effectively run the ls/usr/var .


Fact 6c: When using the exec mode for the ENTRYPOINT instruction, ENTRYPOINT must use the exec mode and for the CMD instruction. If this is not done, Docker will try to add sh -c to the arguments already added, which can lead to some unpredictable results.


Fact 7: The ENTRYPOINT and CMD instructions can be overridden using command line flags.


The --entrypoint flag can be used to override the ENTRYPOINT :


 docker run --entrypoint [my_entrypoint] test 

Everything that follows the image name in the docker run overrides the CMD instruction:


 docker run test [command 1] [arg1] [arg2] 

All of the above facts are true, but keep in mind that developers have the ability to override the flags in the docker run . It follows that ...


Enough facts ... What should I do?


Ok, if you have read the article to this place, here is the information, in which cases use ENTRYPOINT , and in which CMD .


I am going to leave this decision to the person creating the Dockerfile , which can be used by other developers.


Use ENTRYPOINT if you do not want developers to modify the executable file that starts when the container is started. You can imagine that your container is an executable shell . A good strategy would be to define a stable combination of parameters and an executable file as ENTRYPOINT . For it, you can (optionally) specify the default CMD arguments that other developers can use to override.


 $ cat Dockerfile FROM alpine ENTRYPOINT ["ping"] CMD ["www.google.com"] 

 $ docker build -t test . 

Run with default parameters:


 $ 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 ms 

Override 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 ENTRYPOINT (without an ENTRYPOINT definition), if required, so that developers can easily override the executable file. If the entry point is defined, the executable file can still be overridden using the --entrypoint flag. But it will be much more convenient for developers to add the desired command to the end of the docker run .


 $ cat Dockerfile FROM alpine CMD ["ping", "www.google.com"] 

 $ docker build -t test . 

Ping is good, but let's try to run a container with a shell instead of a ping command.


 $ 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 an executable file with a shell or other executable file.


Cleaning


After running the commands, a bunch of stopped containers remained on the host. Clean them with the following command:


 $ docker system prune 

Feedback


I will be glad to hear your thoughts on this article below in the comments. In addition, if you know a simpler way to search for a docker output with jq so that you can do something like docker inspect [id] | jq * .config docker inspect [id] | jq * .config , also write in the comments.


John zaccone


Captain Docker and IBM Cloud Engineer. Specializes in Agile, microservices, containers, automation, REST, DevOps.


References:


  1. ENTRYPOINT vs CMD: Back to Basics

')

Source: https://habr.com/ru/post/329138/


All Articles