Docker Quick Tip: CMD & Entrypoint
First of all, we need to understand the basics of a container’s lifecycle.
Containers are created with a simple purpose, to perform a task. When this task ends, the container is also moved to the “Exited” state.
Knowing that, let’s start the game.
Before starting, if you don’t have the docker installed in your local environment, you can use Play with Docker if you want to practice what will be said in this post.
As a first step, let’s simply run an Alpine Linux image via the docker run command.
docker run alpine
After that, if you run the docker ps command, to list the active containers, in the running state, you will see that we don’t have any containers running.
docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Now, if you run the docker ps command with the -a option, you will notice that we have an Alpine Linux container in the “Exited” state.
docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES441f5a414b40 alpine "/bin/sh" 3 seconds ago Exited (0) 2 seconds ago naughty_elgamal
Note: The -a option shows all containers regardless of status, while the docker run command without -a only shows running containers.
Now, let’s run the run command again, but differently, with some additional instructions.
docker run alpine echo HelloHello
Note that in this case, the output of the execution was the word “Hello”.
This time, if you run the ps -a docker again, you will notice a small change, the value of the COMMAND column.
docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESeffdd406f285 alpine "echo Hello" 2 minutes ago Exited (0) 2 minutes ago keen_euclidb7e953228af4 alpine "/bin/sh" 2 minutes ago Exited (0) 2 minutes ago awesome_chatterjee
To understand what’s happening here, let’s look at two things:
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Note that in addition to the OPTIONS, the RUN command can also receive a COMMAND and ARGS. Knowing this, if we analyze the execution of the command above, with the instructions “echo Hello” and after that, the output of the execution of the command “docker ps -a”, where we notice the change in the value of the COMMAND column, we see that we had a change in the task performed by the container.
Note: In this scenario, “echo” was interpreted as COMMAND and “Hello” as ARGS.
But what happens when I don’t enter COMMAND and (or) ARGS?
To explain this, let’s go-to item 2.
2) Let’s look at the Alpine Image Dockerfile:
FROM scratch
ADD alpine-minirootfs-3.14.2-x86_64.tar.gz /
CMD ["/bin/sh"]
Every image must contain a CMD or Entrypoint, which represents what the container’s task should perform during its lifecycle. In the case of Alpine, we see that the CMD is “/bin/sh”. However, we can override the default CMD during the “docker run” command through COMMAND, as we did in the case of “echo Hello” above.
But where does Entrypoint fit, what is the difference between it and CMD?
To explain this, let’s think about the following scenario, let’s say we’d like to have an image that gets a name and returns the phrase “Hello + the name entered”, but we don’t want to keep passing the command “echo“ every time we run a container to from this image, we just want to insert the name as a parameter.
To exemplify this, let’s create a custom image from Alpine to meet this requirement.
To create our image, the Dockerfile would look like this:
FROM alpineENTRYPOINT ["echo", "Hello"]
With the Dockerfile ready, let’s build our image. In the same directory where the Dockerfile file is located, execute the following command:
docker build -t custom-alpine .
Now to see if our image was created correctly, let’s run the “docker images” command.
docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEalpine latest 14119a10abf4 4 days ago 5.6MBcustom-alpine latest f92503ca5a3f 4 days ago 5.6MB
Now let’s run a container from our custom image, informing a name as an argument.
docker run custom-alpine ThiagoHello Thiago
As per the requirement of the proposed scenario, the name was included as an item in the Entrypoint array and thus the container printed the phrase “Hello Thiago”.
Thus, we can summarize that the main difference between CMD and Entrypoint, is that in the case of Entrypoint you can keep a default command without it being overwritten during the execution of the “docker run” command, whereas in the case of CMD, the command is overwritten.
Now, what if we wanted to keep a default name in case a name is not provided when executing the “docker run” command?
For that, we can combine the use of Entrypoint with CMD, and our Dockerfile would look like this:
FROM alpineENTRYPOINT ["echo", "Hello"]CMD ["Tony Stark"]
With the Dockerfile changed as above, let’s run a build of our image, now generating version 2 of it.
docker build -t custom-alpine:v2
With the build of the image done, let’s now run a container from this new image without informing any name.
docker run custom-alpine:v2Hello Tony Stark
Note that now, without providing a name, the phrase “Hello Tony Stark” is presented as the output of the task performed by the container.
If we run a new container, now informing a name as a parameter, we have the following output:
docker run custom-alpine:v2 ThiagoHello Thiago
Quick tip
In the case of a container orchestrator like Kubernetes, the CMD parameter is replaced through the “args” attribute and the Entrypoint through the “command” attribute, inside the container spec in YAML.
In the case of the example above, we would have something like:
apiVersion: v1
kind: Pod
metadata:
name: custom-alpine-pod
spec:
containers:
- name: custom-alpine-container
image: custom-alpine:v2
command: ["echo", "Hello"]
args: ["Tony Stark"]
Note: This is just an example of working with CMD and Entrypoint in Kubernetes.
Conclusion
Well, I hope I’ve clarified a little bit the difference between CMD and Entrypoint in the lifecycle of Docker containers, when to use one or the other, and when to combine them.