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
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.