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:

  1. Both are separately overridable at runtime;
  2. Either or both may be empty; and
  3. By addition (+), we mean the concatenation of ENTRYPOINT and CMD, respectively, in array context.

A Brief Word on Chamber

To demonstrate the benefit of ENTRYPOINTs, 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.

Michael Fischer

Michael Fischer

Michael Fischer is a Senior DevOps Architect with AWS Professional Services. He specializes in large-scale systems architectures, container design and fleet management, cloud orchestration and automation, and serverless deployments. He lives in Oakland, California and enjoys diving and drums.