ENTRYPOINT vs. CMD

Both instructions allow you to execute a command from within a container, but there is a subtle difference between executing commands via a shell or through exec.  

Consider the following script

shell mode
FROM ubuntu:trusty
#shell mode
CMD ping localhost

Build it with docker build -t demo (if you don't use the -t <name> you will have to use docker tag afterwards).

Then we execute docker run -t demo

Now run docker ps - notice the command that was executed was  "/bin/sh -c 'ping localhost'"

Let's change the script to 

exec mode
FROM ubuntu:trusty
#exec mode
CMD ["ping", "localhost"]

Build it with docker build -t demo .

Then we execute docker run -t demo

Now run docker ps - notice the command that was executed was  "ping localhost"

Shell or exec mode

Shell mode

If you docker exec into the container that has been written CMD in shell mode, then list the processes you see something quite interesting (On Windows we don't actually see the expected result).  If you execute docker exec demo ps -f,  straight after docker run demo, you should get a list of all active processes.  You will notice that the shell is PID 1.   This can be problematic if we need to send any sort of POSIX signals to the container since /bin/sh won't forward signals to child processes (for a detailed write-up, see Gracefully Stopping Docker Containers).

Beyond the PID 1 issue, you may also run into problems with the shell form if you're building a minimal image which doesn't even include a shell binary. When Docker is constructing the command to be run it doesn't check to see if the shell is available inside the container -- if you don't have /bin/sh in your image, the container will simply fail to start.

A better option is to use the exec form of the ENTRYPOINT/CMD instructions which looks like this:

CMD ["executable","param1","param2"]

or

ENTRYPOINT ["executable","param1","param2"]

It is strongly recommended that you always execute CMD and ENTRYPOINT as exec not shell commands.

CMD or ENTRYPOINT

Either of these keywords can be used to kick start the executable when a container is run.  The difference is how parameters are passed to the container:

Using our simple example (exec or shell are applicable)

exec mode
FROM ubuntu:trusty
#exec mode
CMD ["ping", "localhost"]

We can interact with docker using

  • docker run demo hostname
    • overrides the CMD value in the Dockerfile
    • returns the host's name
  • docker run demo ls -al
    • overrides the CMD values in the Dockerfile
    • lists all the files in the landing directory, notice the param "-al" is passed to the "ls" command
  • docker run --entrypoint hostname demo
    • returns the host's name
  • docker run --entrypoint ls demo -al
    • lists all the files in the landing directory, notice the param "-al" is passed to the "ls" command

Notice the subtle difference in how parameters to the command you wish to execute are passed.  With ENTRYPOINT, the command is specified after the --entrypoint switch, but params to the command are passed after the image name.

Combining CMD with ENTRYPOINT

docker file
FROM ubuntu:trusty
ENTRYPOINT ["/bin/ping", "-c", "3"]
CMD ["localhost"]

When ENTRYPOINT is combined with CMD, the CMD values are appended to the ENTRYPOINT values, so the command that is executed is "/bin/ping -c 3 localhost"

Remember that if we use the following syntax docker run -t <image> <params, ...>, this is a CMD override and not an ENTRYPOINT override.  So if you execute docker run -t demo bbc.co.uk, the bbc.co.uk value replaces the CMD value of localhost.  Overriding ENTRYPOINT command parameters can be done without overriding the command and its parameters, see below.

Some useful tricks

Consider the following command docker run -it --entrypoint bash demo, this will run the image and shell you into the image.  Tis works because the bash command expects no other parameters.

What about the following, create a text file called "inputfile.txt" in the host with the follwing entries /lib /bin /usr /sbin, all with line breaks after them, then execute the command docker run -it --entrypoint ls demo -al $(cat inputfile.txt).  This cool little command will cat the inputfile.txt prodducing /lib<cr/lf> /bin<cr/lf> /usr<cr/lf> /sbin<cr/lf> and pass this as a parameter to the command, so the command would actually be  docker run -it --entrypoint ls demo -al /lib<cr/lf> /bin<cr/lf> /usr<cr/lf> /sbin<cr/lf>

Conclusion and advice

Use CMD in conjunction with ENTRYPOINT

Use exec rather than shell when writing your Dockerfile