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.