AWS Open Source Blog
Demystifying ENTRYPOINT and CMD in Docker
中文版 – As you begin your Docker container creation journey, you might find yourself faced with a puzzling question: Should your Dockerfile contain an ENTRYPOINT
instruction, a CMD
instruction, or both? In this post, I discuss the differences between the two in detail, and explain how best to use them in various use cases you might encounter.
General Rule
If there’s one lesson you should take away today, it’s the following general rule:
ENTRYPOINT
+ CMD
= default container command arguments
Subject to the following:
- Both are separately overridable at runtime;
- Either or both may be empty; and
- By addition (+), we mean the concatenation of
ENTRYPOINT
andCMD
, respectively, in array context.
A Brief Word on Chamber
To demonstrate the benefit of ENTRYPOINT
s, we introduce Chamber, an open-source utility that populates the container’s environment with values found in AWS Systems Manager Parameter Store. A typical invocation is chamber exec production -- program
, which fetches all Parameter Store values whose keys are prefixed with /production
, converts slashes in the keys to underscores, and populates the environment with the returned keys and values. For example, if a key /production/mysql/password
is found, then Chamber would set the MYSQL_PASSWORD
environment variable to the secure value within.
The Simplest Example
Let’s start with an example. Here’s a Dockerfile
snippet that has both an ENTRYPOINT
and a CMD
, both specified as arrays:
ENTRYPOINT ["/bin/chamber", "exec", "production", "--"]
CMD ["/bin/service", "-d"]
Putting these together, the default arguments to the container will be ["/bin/chamber", "exec", "production", "--", "/bin/service", "-d"]
.
This list roughly approximates the shell command /bin/chamber exec production -- /bin/service -d
. (In fact, this relates to what shells primarily do: they take space-separated “commands” at the prompt, and ultimately turn them into arrays of arguments for passing to the exec system call.)
Arguments are Always Arrays
It’s important to understand that, in a Dockerfile
, ENTRYPOINT,
and CMD
are always converted to arrays — even if you declare them as strings. (I always recommend declaring them as arrays, though, to avoid ambiguity.)
Suppose we declare a CMD
that starts a web server as follows:
CMD /usr/bin/httpd -DFOREGROUND
Docker will automatically convert CMD
to an array that looks like this:
["/bin/sh", "-c", "/usr/bin/httpd -DFOREGROUND"]
The same is true for ENTRYPOINT
as well.
So when we declare both an ENTRYPOINT
and a CMD
, and ENTRYPOINT
is a list, the two are concatenated together to form a default argument list — even if we declare CMD
as a string.
Here’s an example that illustrates the point. If we declare the following:
ENTRYPOINT ["/bin/chamber", "exec", "production", "--"]
CMD "/bin/service -d"
The default argument list will be ["/bin/chamber", "exec", "production", "--", "/bin/sh", "-c", "/bin/service -d"].
Note: ENTRYPOINT
and CMD
cannot both be string values. They can both be array values, and ENTRYPOINT
can be an array value and CMD
can be a string value; but if ENTRYPOINT
is a string value, CMD
will be ignored. This is an unfortunate but unavoidable consequence of the way argument strings are converted to arrays. This is among the reasons I always recommend specifying arrays whenever possible.
CMD is Merely a Default
Specifying CMD
in a Dockerfile
merely creates a default value: if we pass non-option arguments to docker run
, they will override the value of CMD
.
To illustrate, suppose we have the following Dockerfile
and create an image from it called myservice
:
ENTRYPOINT ["/bin/chamber", "exec", "production", "--"]
CMD ["/bin/service", "-d"]
If we invoke docker run myservice
, the container will be created with the following arguments:
["/bin/chamber", "exec", "production", "--", "/bin/service", "-d"]
If we instead invoke docker run myservice /bin/debug
, the container will be created with these arguments instead:
["/bin/chamber", "exec", "production", "--", "/bin/debug"]
Note that CMD
is entirely replaced — it’s not prepended or appended.
ENTRYPOINT is Also Overridable
We can easily override the ENTRYPOINT
declared in a Dockerfile
as well. To do so, we specify the --entrypoint
option argument to docker run
.
Suppose, as before, we have the following Dockerfile
and create an image from it called myservice
:
ENTRYPOINT ["/bin/chamber", "exec", "production", "--"]
CMD ["/bin/service", "-d"]
Now, let’s change the ENTRYPOINT
by running the following:
docker run --entrypoint /bin/logwrap myservice
Per our general rule, the following argument list will be constructed:
["/bin/logwrap", "/bin/service", "-d"]
Overriding Both ENTRYPOINT and CMD
Can we override both ENTRYPOINT
and CMD
? Certainly:
docker run --entrypoint /bin/logwrap myservice /bin/service -e
Here’s the corresponding argument list — by this point there should be no surprises:
["/bin/logwrap", "/bin/service", "-e"]
When Should I Use ENTRYPOINT? How About CMD?
Supposed we’re building our own Dockerfile
for a project. At this point, we understand the mechanics of how ENTRYPOINT
and CMD
work together to construct a default argument list for a container. But now we need to know which to choose: When is it better to use ENTRYPOINT
, and when is it better to use CMD
?
The choice you make is largely an artistic one, and it will depend significantly on your use case. My experience, though, is that ENTRYPOINT
suits almost every case I’ve encountered. Consider the following use cases:
Wrappers
Some images contain a so-called “wrapper” that decorates a legacy program or otherwise prepares it for use in a containerized environment. For example, suppose your service was written to read its configuration from a file instead of from environment variables. In such a situation, you might include a wrapper script that generates the app’s config file from the environment variables, then launches the app by calling exec /path/to/app
at the very end.
Declaring an ENTRYPOINT
that points to the wrapper is a great way to ensure the wrapper is always run, no matter what arguments are passed to docker run
.
Single-Purpose Images
If your image is built to do only one thing — for example, run a web server — use ENTRYPOINT
to specify the path to the server binary and any mandatory arguments. A textbook example of this is the nginx
image, whose sole purpose is to run the nginx web server. This lends itself to a pleasant and natural command line invocation: docker run nginx
. Then you can append program arguments naturally on the command line, such as docker run nginx -c /test.conf
– just like you would if you were running nginx without Docker.
Multi-Mode Images
It’s also a common pattern for images that support multiple “modes” to use the first argument to docker run <image>
to specify a verb that maps to the mode, such as shell
, migrate
, or debug
. For such use cases I recommend setting ENTRYPOINT
to point to a script that parses the verbal argument and does the right thing based on its value, such as:
ENTRYPOINT ["/bin/parse_container_args"]
The arguments will passed to the entrypoint on invocation via ARGV[1..n]
, or $1
, $2
, etc.
Conclusion
Docker has extremely powerful and flexible image-building functionality, and it can be challenging to decide exactly how to construct a container’s default runtime arguments. I hope this article provided clarity on how the argument-assembly mechanics work, and how to leverage them to best effect in your environment.