CMD vs ENTRYPOINT
In this tutorial we will discuss about CMD vs ENTRYPOINT in Docker.
Real time example
Let’s start with a simple scenario. Say you were to run a docker container from an Ubuntu image.
When you run the docker run command it runs an instance of Ubuntu image and exists immediately.
If you were to list the running containers you wouldn’t see the container running. If you list all containers including those that are stopped you will see that the new container you ran is in an existed state.
$ docker run ubuntu $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 10d1a2757900 ubuntu "/bin/bash" 41 seconds ago Exited (0) 40 seconds ago confident_thompson
Now why is that? unlike virtual machines, Containers are not meant to host an operating system.
Container are meant to run specific task or process such as to host an instance of a web server or application server or a database or simply to carry out some kind of computation or analysis.
Once the task is complete the container exits. The container only lives as long as the process inside it is alive. If the web service inside the container is stopped or crashes the container exits.
Who defines what process is run within the container?
If you look at the Docker file for popular images like NGINX you will see an instruction called CMD which stands for command that defines the program that will be run within container when it starts for the NGINX image.
What we tried to do earlier was to run a container with a plain Ubuntu operating system. Let’s look at the Docker file for this image you will see the that it uses bash as the default command.
# Pull base image. FROM ubuntu:14.04 # Install. RUN \ sed -i 's/# \(.*multiverse$\)/\1/g' /etc/apt/sources.list && \ apt-get update && \ apt-get -y upgrade && \ apt-get install -y build-essential && \ apt-get install -y software-properties-common && \ apt-get install -y byobu curl git htop man unzip vim wget && \ rm -rf /var/lib/apt/lists/* # Add files. ADD root/.bashrc /root/.bashrc ADD root/.gitconfig /root/.gitconfig ADD root/.scripts /root/.scripts # Set environment variables. ENV HOME /root # Define working directory. WORKDIR /root # Define default command. CMD ["bash"]
Now bash is not really a process like your web server or database server. It is a shell that listens for inputs from a terminal. If it cannot find a terminal it exits.
When we ran the Ubuntu container earlier, docker created a container from the Ubuntu image and launched the bash program.
By default docker doesn’t attach a terminal to a container when it is run. And so the bash program doesn’t find the terminal and so it exits.
Since the process that was started when the container was created finished the container exits as well.
How do you specify a different command to start a container?
One option is to append a command to the docker run command and that way it overrides the default command specified within the image.
In our case, I run the docker run ubuntu command with the sleep 10 command as the added option.
$ docker run ubuntu sleep 10
This way when the container starts it runs the sleep program waits to 10 seconds and then exits. But how do we make that change permanent?
Say we want the image to always run the sleep command when it starts. You would then create create your own image from the base Ubuntu image and specify new command.
FROM ubuntu CMD sleep 10
There are different ways to specifying the command either the command simply as is in a shell form or in JSON array format like as follows.
But remember when you specify in a JSON array format, the first element in the array should be the executable.
Note that don’t specify the command and parameters together like “sleep 10”. The command and its parameters should be separate elements in the list.
So now I build my new image using docker build command and name it as ubuntu-sleeper. I could now simple run the docker run ubuntu-sleeper command and gets the same results.
$ docker build -t ubuntu-sleeper:1.0.0 $ docker run ubuntu-sleeper:1.0.0
It always sleep for 10 seconds and exits. But what if I wish to change the number of seconds it sleeps. Currently it hard coded to 10 seconds. As we discussed before, one option is to run the docker run command with the new command appended to it.
In this case sleep 20 and the command that will be run at startup will be sleep 20 but it doesn’t look very good.
$ docker run ubuntu-sleeper:1.0.0 sleep 20
The name of the image ubuntu-sleeper in itself that the container will sleep. So we shouldn’t have to specify the sleep command again.
Instead we would like it to be something like below
$ docker run ubuntu-sleeper 20
We only want to pass in the number of seconds the container should sleep and sleep command should be invoked automatically and this is where the ENTRYPOINT instructions comes into play.
FROM ubuntu ENTRYPOINT ["sleep"]
The ENTRYPOINT instruction is like the CMD instruction as in you can specify the program that will be run when the container starts.
And whatever you specify on the command line, in this case 20 will get appended to the ENTRYPOINT (like sleep 20).
So the commands that will be run when the container starts is sleep 20. So that’s the different between ENTRYPOINT and CMD.
In case of CMD instruction the command line parameters passed will get replaced entirely where as in case of ENTRYPOINT the command line parameters will get appended.
Now in the second case what if we run the ubuntu-sleeper image command without appending the number of seconds? then the command at startup will be just sleep and you get the error that the operand is missing.
So, how do we configure a default value of the command? If one was not specified in the command line that’s where you would use ENTRYPOINT as well as the command instruction.
FROM ubuntu ENTRYPOINT ["sleep"] CMD ["5"]
In this case the CMD instruction will be appended to the ENTRYPOINT instruction.
So at startup the command would be “sleep 5”, if you didn’t specify any parameters in the command line. If you did then that will override the CMD instruction.
Please note that, for this to happen you should always specify the ENTRYPOINT and CMD instructions in a JSON format.
Finally what if you feel you really want to modify the ENTRYPOINT during runtime, say from sleep to an imaginary sleep 2.0 command?
Well in that case you can override it by using the ENTRYPOINT option in the docker run command.
$ docker run --entrypoint sleep2.0 ubuntu-sleeper 30
I hope after reading this tutorial, you should have a better understanding of the difference between ENTRYPOINT and CMD. Explore the use of both and experiment with them to find the best solution for you.