How-to's and Support

Understanding Docker's CMD and ENTRYPOINT Instructions

Written by: Ben Cane

7 min read

Updated on April 20, 2025

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.

ENTRYPOINT overview

The Docker ENTRYPOINT instruction sets the executable command that is run each time a container starts, effectively transforming the container into an executable program. This is especially beneficial when the container is intended to function consistently as a single-purpose application. By using the ENTRYPOINT instruction, any arguments passed during the container startup via the docker run command are appended to the defined executable, streamlining container usage.

Utilizing the ENTRYPOINT instruction also allows the container to execute its main process as PID 1, providing efficient handling of operating system signals such as SIGTERM and SIGINT. This ensures a more stable and manageable lifecycle for containers, facilitating smoother shutdowns and interruptions. It is important to note that overriding the ENTRYPOINT command at runtime requires explicit use of Docker's --entrypoint option, highlighting its intended use for fixed execution behaviors.

CMD overview

The Docker CMD instruction specifies default commands or parameters that execute when the container starts. It provides users flexibility, allowing these defaults to be easily overridden during container runtime. This makes the CMD instruction particularly useful for scenarios in which a container requires flexible command execution or parameterization.

However, commands set with the CMD instruction typically do not run as PID 1 unless used in conjunction with the ENTRYPOINT instruction, which can result in less efficient handling of system signals. Therefore, the CMD instruction is better suited for providing adjustable defaults rather than defining fixed commands intended for robust system signal management.

In essence, the CMD instruction is ideal for situations that demand flexibility and adaptability, whereas ENTRYPOINT excels in enforcing consistent behavior with the option to parameterize through command-line arguments.

Docker container example: 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 &amp;&amp; \
    apt-get install -y apache2-utils &amp;&amp; \
    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 it and other application containers.

However, before we can run this container, 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 &amp;&amp;     apt-get install -y apache2-utils &amp;&amp;     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

Using ENTRYPOINT instructions

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 &amp;&amp; \
    apt-get install -y apache2-utils &amp;&amp; \
    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 &amp;&amp;     apt-get install -y apache2-utils &amp;&amp;     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.

The importance of syntax when using ENTRYPOINT

One important thing to note 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 container's behavior.

FROM ubuntu:latest
RUN apt-get update &amp;&amp; \
    apt-get install -y apache2-utils &amp;&amp; \
    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 &amp;&amp;     apt-get install -y apache2-utils &amp;&amp;     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.

Final thoughts on using CMD and ENTRYPOINT in Dockerfiles

Understanding the differences between Docker's CMD and ENTRYPOINT instructions is essential for creating flexible, reliable, and efficient containers. While CMD offers versatility by allowing default commands to be overridden easily, ENTRYPOINT provides consistency by ensuring a specified command runs every time a container starts.

Mastering these instructions enables developers to optimize their Docker containers effectively for various use cases. For more detailed examples and further customization possibilities, explore Docker's official Dockerfile reference documentation.

Stay up-to-date with the latest insights

Sign up today for the CloudBees newsletter and get our latest and greatest how-to’s and developer insights, product updates and company news!