In a Dockerfile, we often encounter instructions like run, cmd, or entrypoint. At first glance, they are all used for specifying and running commands. But what's the difference between them? And how do they interact with one another?
In this tutorial, we'll answer these questions. We'll present what each of these instructions does and how they work. We'll also look at what role they play in building an image and running a Docker container.
To start, let's create a script, log-event.sh. It simply adds one line to a file and then prints it:
#!/bin/sh echo `date` [email protected] >> log.txt; cat log.txt;
And now, let's create a simple Dockerfile:
FROM alpine ADD log-event.sh /
It'll make use of our script by appending lines to log.txt in different scenarios.
3. The run Command
The run instruction executes when we build the image. That means the command passed to run executes on top of the current image in a new layer. Then the result is committed to the image. Let's see how this looks in action.
Firstly, we'll add a run instruction to our Dockerfile:
FROM alpine ADD log-event.sh / RUN ["/log-event.sh", "image created"]
Secondly, let's build our image with:
docker build -t myimage .
Now we expect to have a Docker image containing a log.txt file with one image created line inside. Let's check this by running a container based on the image:
docker run myimage cat log.txt
When listing the contents of the file, we'll see an output like this:
Fri Sep 18 20:31:12 UTC 2020 image created
If we run the container several times, we'll see that the date in our log file doesn't change. This makes sense because the run step executes at image build time, not at the container runtime.
Let's now build our image again. We notice the creation time in our log didn't change. This happens because Docker caches the result for the run instruction if the Dockerfile didn't change. If we want to invalidate the cache, we need to pass the –no-cache option to the build command.
4. The cmd Command
With the cmd instruction, we can specify a default command that executes when the container is starting. Let's add a cmd entry to our Dockerfile and see how it works:
... RUN ["/log-event.sh", "image created"] CMD ["/log-event.sh", "container started"]
After building the image, let's now run it and check the output:
$ docker run myimage Fri Sep 18 18:27:49 UTC 2020 image created Fri Sep 18 18:34:06 UTC 2020 container started
If we run this multiple times, we'll see that the image created entry stays the same. But the container started entry updates with every run. This shows how cmd indeed executes every time the container starts.
Notice we've used a slightly different docker run command to start our container this time. Let's see what happens if we run the same command as before:
$ docker run myimage cat log.txt Fri Sep 18 18:27:49 UTC 2020 image created
This time the cmd specified in the Dockerfile is ignored. That's because we have specified arguments to the docker run command.
Let's move on now and see what happens if we have more than one cmd entry in the Dockerfile. Let's add a new entry that will display another message:
... RUN ["/log-event.sh", "image created"] CMD ["/log-event.sh", "container started"] CMD ["/log-event.sh", "container running"]
After building the image and running the container again, we'll find the following output:
$ docker run myimage Fri Sep 18 18:49:44 UTC 2020 image created Fri Sep 18 18:49:58 UTC 2020 container running
As we can see, the container started entry is not present, only the container running is. That's because only the last cmd is invoked if more than one is specified.
5. The entrypoint Command
As we saw above, cmd is ignored if passing any arguments when starting the container. What if we want more flexibility? Let's say we want to customize the appended text and pass it as an argument to the docker run command. For this purpose, let's use entrypoint. We'll specify the default command to run when the container starts. Moreover, we're now able to provide extra arguments.
Let's replace the cmd entry in our Dockerfile with entrypoint:
... RUN ["/log-event.sh", "image created"] ENTRYPOINT ["/log-event.sh"]
Now let's run the container by providing a custom text entry:
$ docker run myimage container running now Fri Sep 18 20:57:20 UTC 2020 image created Fri Sep 18 20:59:51 UTC 2020 container running now
We can see how entrypoint behaves similarly to cmd. And in addition, it allows us to customize the command executed at startup.
Like with cmd, in case of multiple entrypoint entries, only the last one is considered.
6. Interactions Between cmd and entrypoint
We have used both cmd and entrypoint to define the command executed when running the container. Let's now move on and see how to use cmd and entrypoint in combination.
One such use-case is to define default arguments for entrypoint. Let's add a cmd entry after entrypoint in our Dockerfile:
... RUN ["/log-event.sh", "image created"] ENTRYPOINT ["/log-event.sh"] CMD ["container started"]
Now, let's run our container without providing any arguments, and with the defaults specified in cmd:
$ docker run myimage Fri Sep 18 21:26:12 UTC 2020 image created Fri Sep 18 21:26:18 UTC 2020 container started
We can also override them if we choose so:
$ docker run myimage custom event Fri Sep 18 21:26:12 UTC 2020 image created Fri Sep 18 21:27:25 UTC 2020 custom event
Something to note is the different behavior of entrypoint when used in its shell form. Let's update the entrypoint in our Dockerfile:
... RUN ["/log-event.sh", "image created"] ENTRYPOINT /log-event.sh CMD ["container started"]
In this situation, when running the container, we'll see how Docker ignores any arguments passed to either docker run or cmd.
In this article, we've seen the differences and similarities between the Docker instructions: run, cmd, and entrypoint. We've observed at what point they get invoked. Also, we've taken a look at their uses and how they work together.