Understanding Docker’s CMD and ENTRYPOINT Instructions

DevelopmentDocker Commands

Reading Time: 6 minutes

When creating a Docker container, the goal is generally that anyone could simply execute docker run <containername> and launch the container. In today’s article, we are going to explore two key Dockerfile instructions that enable us to do just that. Let’s explore the differences between the CMD and ENTRYPOINT instructions.

On the surface, the CMD and ENTRYPOINT instructions look like they perform the same function. However, once you dig deeper, it’s easy to see that these two instructions perform completely different tasks.

ApacheBench Dockerfile

To help serve as an example, we’re going to create a Docker container that simply executes the ApacheBench utility.

In earlier articles, we discovered the simplicity and usefulness of the ApacheBench load testing tool. However, this type of command-line utility is not generally the type of application one would “Dockerize.” The general usage of Docker is focused more on creating services rather than single execution tools like ApacheBench.

The main reason behind this is that typically Docker containers are not built to accept additional parameters when launching. This makes it tricky to use a command-line tool within a container.

Let’s see this in action by creating a Docker container that can be used to execute ApacheBench against any site.

FROM ubuntu:latest

RUN apt-get update && \
    apt-get install -y apache2-utils && \
    rm -rf /var/lib/apt/lists/*

CMD ab

In the Dockerfile, we are simply using the ubuntu:latest image as our base container image, installing the apache2-utils package, and then defining that the command for this container is the ab command.

Since this Docker container is planned to be used as an executor for the ab command, it makes sense to set the CMD instruction value to the ab command. However, if we run this container we will start to see an interesting difference between this container and other application containers.

Before we can run this container, however, we first need to build it. We can do so with the docker build command.

$ docker build -t ab .
Sending build context to Docker daemon 2.048 kB
Step 1/3 : FROM ubuntu:latest
 ---> ebcd9d4fca80
Step 2/3 : RUN apt-get update &&     apt-get install -y apache2-utils &&     rm -rf /var/lib/apt/lists/*
 ---> Using cache
 ---> d9304ff09c98
Step 3/3 : CMD ab
 ---> Using cache
 ---> ecfc71e7fba9
Successfully built ecfc71e7fba9

When building this container, I tagged the container with the name of ab. This means we can simply launch this container via the name ab.

$ docker run ab
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds

When we run the ab container, we get back an error from the ab command as well as usage details. The reason for this is that we defined the CMD instruction to the ab command without specifying any flags or target host to load test against. This CMD instruction is used to define what command the container should execute when launched. Since we defined that as the ab command without arguments, it executed the ab command without arguments.

However, like most command-line tools, that simply isn’t how ab works. With ab, you need to specify what URL you wish to test against.

What we can do in order to make this work is override the CMD instruction when we launch the container. We can do this by adding the command and arguments we wish to execute at the end of the docker run command.

$ docker run ab ab http://bencane.com/
Benchmarking bencane.com (be patient).....done
Concurrency Level:      1
Time taken for tests:   0.343 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      98505 bytes
HTML transferred:       98138 bytes
Requests per second:    2.92 [#/sec] (mean)
Time per request:       342.671 [ms] (mean)
Time per request:       342.671 [ms] (mean, across all concurrent requests)
Transfer rate:          280.72 [Kbytes/sec] received

When we add ab http://bencane.com to the end of our docker run command, we are able to override the CMD instruction and execute the ab command successfully. However, while we were successful, this process of overriding the CMD instruction is rather clunky.

!Sign up for a free Codeship Account

ENTRYPOINT

This is where the ENTRYPOINT instruction shines. The ENTRYPOINT instruction works very similarly to CMD in that it is used to specify the command executed when the container is started. However, where it differs is that ENTRYPOINT doesn’t allow you to override the command.

Instead, anything added to the end of the docker run command is appended to the command. To understand this better, let’s go ahead and change our CMD instruction to the ENTRYPOINT instruction.

FROM ubuntu:latest

RUN apt-get update && \
    apt-get install -y apache2-utils && \
    rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["ab"]

After editing the Dockerfile, we will need to build the image once again.

$ docker build -t ab .
Sending build context to Docker daemon 2.048 kB
Step 1/3 : FROM ubuntu:latest
 ---> ebcd9d4fca80
Step 2/3 : RUN apt-get update &&     apt-get install -y apache2-utils &&     rm -rf /var/lib/apt/lists/*
 ---> Using cache
 ---> d9304ff09c98
Step 3/3 : ENTRYPOINT ab
 ---> Using cache
 ---> aa020cfe0708
Successfully built aa020cfe0708

Now, we can run the ab container once again; however, this time, rather than specifying ab http://bencane.com, we can simply add http://bencane.com to the end of the docker run command.

$ docker run ab http://bencane.com/
Benchmarking bencane.com (be patient).....done
Concurrency Level:      1
Time taken for tests:   0.436 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      98505 bytes
HTML transferred:       98138 bytes
Requests per second:    2.29 [#/sec] (mean)
Time per request:       436.250 [ms] (mean)
Time per request:       436.250 [ms] (mean, across all concurrent requests)
Transfer rate:          220.51 [Kbytes/sec] received

As the above example shows, we have now essentially turned our container into an executable. If we wanted, we could add additional flags to the ENTRYPOINT instruction to simplify a complex command-line tool into a single-argument Docker container.

Be careful with syntax

One imporant thing to call out about the ENTRYPOINT instruction is that syntax is critical. Technically, ENTRYPOINT supports both the ENTRYPOINT ["command"] syntax and the ENTRYPOINT command syntax. However, while both of these are supported, they have two different meanings and change how ENTRYPOINT works.

Let’s change our Dockerfile to match this syntax and see how it changes our containers behavior.

FROM ubuntu:latest

RUN apt-get update && \
    apt-get install -y apache2-utils && \
    rm -rf /var/lib/apt/lists/*

ENTRYPOINT ab

With the changes made, let’s build the container.

$ docker build -t ab .
Sending build context to Docker daemon 2.048 kB
Step 1/3 : FROM ubuntu:latest
 ---> ebcd9d4fca80
Step 2/3 : RUN apt-get update &&     apt-get install -y apache2-utils &&     rm -rf /var/lib/apt/lists/*
 ---> Using cache
 ---> d9304ff09c98
Step 3/3 : ENTRYPOINT ab
 ---> Using cache
 ---> bbfe2686a064
Successfully built bbfe2686a064

With the container built, let’s run it again using the same options as before.

$ docker run ab http://bencane.com/
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds

It looks like we are back to the same behavior as the CMD instruction. However, if we try to override the ENTRYPOINT we will see different behavior than when we overrode the CMD instruction.

$ docker run ab ab http://bencane.com/
ab: wrong number of arguments
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds

With the ENTRYPOINT instruction, it is not possible to override the instruction during the docker run command execution like we are with CMD. This highlights another usage of ENTRYPOINT, as a method of ensuring that a specific command is executed when the container in question is started regardless of attempts to override the ENTRYPOINT.

Summary

In this article, we covered quite a bit about CMD and ENTRYPOINT; however, there are still additional uses of these two instructions that allow you to customize how a Docker container starts. To see some of these examples, you can take a look at Docker’s Dockerfile reference docs.

With the above example however, we now have a way to “Dockerize” simple command-line tools such as ab, which opens up quite a few interesting use cases. If you have one, feel free to share it in the comments below.

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles.
Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.



We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

  • taowang2218

    It is possible to override the `ENTRYPOINT` during the `docker run`, just not as elegant as overriding `CMD`.

    In the last case, you can use `docker run –entrypoint=”ab” ab http://bencane.com/`.