Dockerfile RUN vs. CMD vs. ENTRYPOINT

What do these Dockerfile instructions have in common, and what are the differences? When should I use each instruction?

July 15, 2020 in Docker

One topic that constantly leads to confusion amongst beginners is the usage of the RUN, CMD and ENTRYPOINT Dockerfile instructions. This is probably due to the fact that all of these instructions have one thing in common: Each of them runs a CLI command or an executable file, respectively. But apart from that, they fulfill an entirely different purpose.

The RUN Instruction

RUN is the instruction that differs the most from the other ones. A Dockerfile aims to create a filesystem that allows the user to run their project or service seamlessly. To achieve this state, there are some things that need to be done: Files and directories have to be created, packages have to be installed and dependencies have to be downloaded. All this is done using the RUN instruction.

Because RUN modifies the underlying filesystem, each instruction creates a new image layer that contains the modification to the filesystem. The modification associated with an instruction like RUN npm install would be a freshly created node_modules directory for example.

Thus, RUN executes:

  • commands you normally would run as an administrator to set up the environment.
  • commands you normally would run as a developer to get your project up and running.

The CMD Instruction

In contrast to RUN, there's only a single CMD instruction per image. This is because CMD defines a default command that should be executed when starting a container from the image:

FROM alpine:3.9
CMD ["echo", "Hello from CMD"]

This Dockerfile uses Alpine Linux as base image and executes the echo command as you run a corresponding container. In the so-called exec form used here, all arguments for the command are appended as strings and separated by a comma. You could use any command or executable file here, such as a self-compiled binary for instance.

After creating an image using docker image build -t my-alpine ., running a container will yield the following output:

$ docker container run my-alpine
  Hello from CMD

The special feature of CMD is that it can be overridden by docker container run. A look at the command reference shows that the syntax actually is docker container run [OPTIONS] IMAGE [COMMAND] [ARG...], meaning it takes an optional command argument after the image name.

Providing such a command overrides the CMD instruction defined in the Dockerfile.

$ docker container run my-alpine echo "Hello from the CLI"
  Hello from the CLI

This particular feature will be important in the next section.

The ENTRYPOINT Instruction

Just like CMD, ENTRYPOINT defines a starting command for containers as well. However, it is usually not overridden by docker container run. Entry points are a good fit for containers that always run the same service and act as an executable.

What matters the most, though, is that CMD is always appended to ENTRYPOINT.

You can verify this using the following Dockerfile, containing both instructions:

FROM alpine:3.9
ENTRYPOINT ["echo"]
CMD ["Hello from CMD"]

Re-building the image and running a container produces the same output, despite the fact that echo and its arguments have been split apart.

$ docker container run my-alpine
  Hello from CMD

Keep in mind that CMD is still overridable. Since CMD includes the arguments for echo, you can override these arguments from the command line now. They will be appended to ENTRYPOINT just as before.

$ docker container run my-alpine "Hello from the CLI"
  Hello from the CLI

This pattern is very popular: ENTRYPOINT defines a fixed command that is always executed, and CMD defines default arguments for this command - overridable by docker container run.

Overriding ENTRYPOINT

In the previous section, I stated that the ENTRYPOINT instruction is usually not overridden by docker container run. It was a deliberate decision that overriding the entry point is not as easy as overriding the default command. This is because ENTRYPOINT allows a container to be used as if it were the actual binary running inside of it.

You can override an entry point nonetheless:

$ docker container run --entrypoint /bin/ls my-alpine /

Keep in mind that --entrypoint just takes the binary to be executed - all arguments for that binary still need to be appended to the image name. Therefore, the shown command evaluates to /bin/ls /.

Bottom Line

The use case for RUN should be clear.

ENTRYPOINT should be used for containers whose sole purpose is to run a service. While the actual command should be defined as entry point, default arguments for that command can be provided using CMD and overridden by the user if required.

A single CMD instruction without any entry point can be useful when a particular command should be executed by default, being completely overridable by the user at any time.

💫graph: A library for creating generic graph data structures and modifying, analyzing, and visualizing them.
💫
graph: A library for creating generic graph data structures and modifying, analyzing, and visualizing them.