Container Entrypoint

Datetime:2016-08-23 03:16:00          Topic: Docker           Share

Introduction

This time in our series about containers we are going to look a bit more closely into what happens when a container starts and how we can influence that. Specifically we’re going to learn about the ENTRYPOINT and the CMD keywords in a Dockerfile and among other things how they relate to each other.

The Entry Point

Whenever we start a container from an image we need to declare what command(s) shall be executed by the container upon start. This is called the entry point. Usually when we define a Dockerfile we specify such an entry point as (one of the) last items in the declaration. As a sample let’s take a Dockerfile for an overly simple Python application consisting of one single file app.py with code. Usually, when running the Python application directly on our development machine – that is, not in a container – we just start the application using this command

python app.py

consequently our Dockerfile would look like this

note the last line declaring the ENTRYPOINT . The syntax I have chosen in the above sample is one possible way of declaring the entry point. An alternative syntax is using an array of words, i.e.

ENTRYPOINT ["python", "app.py"]

According to the documentation the latter is the preferred way but honestly I find the former much more readable; yet you will see further below why the array syntax makes sense or in certain scenarios is even the only way things are working.

For the moment let’s assume that app.py contains the following code

We can now build an image using the Dockerfile

docker build -t python-sample .

And we should see something like this

Now we can run a container from our new image like this

docker run -it --rm python-sample

and we should see this output

Overriding the Entry Point

From time to time we want to run a container from an image that has an ENTRYPOINT defined but we want to override this entry point with our own. Luckily the Docker CLI allows us to just do that by using the --entrypoint argument when running a container. As an example: if we want to run a container from our python-sample image but would rather run a bash shell upon start of the container we can use a run command like this

docker run -it --rm --entrypoint /bin/bash python-sample

This possibility to override the entry point cannot be overestimated. It is very very helpful in many scenarios. For me personally the most common use case is to trouble shoot a container by overriding the entry point with a call of a bash shell instead of the predefine entry point. If I am dealing with a simpler image that doesn’t have bash installed like e.g. the Alpine Linux container then I just use the default shell /bin/sh .

Another use case is to run tests. Let’s assume we have a file tests.py in our project which contains some tests.

We can then run a container in test mode as follows

docker run -dt --name test --entrypoint python tests.py python-sample

That is really helpful! We can use one and the same container image to generate ontainers that do slightly or completely different things.

The same also works when we use docker-compose . Assume we have the following docker-compose.yml file in our project

On line 5 you can see how we declare what entry point to use when starting the container.

The CMD Keyword

Docker also defined a keyword CMD that can be used in a Dockerfile . What we declare as a command is also used during startup of a container. OK, that sounds confusing… Don’t worry, it took my a while too to finally find a good description defining the differences between ENTRYPOINT and CMD .

Let’s illustrate that with a sample. For this sample I am going to use the official Alpine Linux image since it is so small and has everything I need for this demonstration. Let me first start with the following command

docker run --rm -it alpine echo "Hello World"

Here we are running an instance of the alpine image in interactive mode and we tell Docker what command shall be executed upon start of the container. The result as we can see below is as expected, the container just outputs “Hello World” on the terminal. Since we have used --rm in the command the container will be automatically destroyed after its execution has stopped.

Please note that a container stops as soon as the main process running inside the container is terminated. In our simple sample this is of course the case immediately after echoing “Hello World”.

We can now achieve the same with a slightly modified command

docker run --rm -it --entrypoint echo alpine "Hello World"

So, what just happened? We defined the start command using --entrypoint to be the echo command. The part after the image name (alpine) in this case is only the “Hello World” string. It is passed as parameter to the start command. As a result the container is started by Docker with the command echo "Hello World" . Now, what’s the difference between the former and the latter? In the former case we did not declare an entry point. In this case Docker automatically assumes /bin/sh -c as start command and passed to it (as string) whatever we define in our docker run command after the image name. In the former case this is echo "Hello World" . Thus that container was started with

/bin/sh -c "echo \"Hello World\"" .

Now let’s work with a Dockerfile . Our first version of the file looks like this, that is we continue with our Alpine image and the “Hello World” message

let’s create an image from this

docker build -t my-hello-world .

and run a container of this image

docker run --rm -it my-hello-world

what we should see is

In this sample we did not explicitly define an entry point, thus Docker assumes automatically /bin/sh -c . But we define the parameters with the keyword CMD to be passed to the (implicit) entry point.

According to what I told above we should now also be able to achieve the same result with the following Dockerfile variant

and indeed after building the image and running a container we have the same end result

docker build -t my-hello-world-2 .

docker run --rm -it my-hello-world-2

Please note that in the Dockerfile we have to use the array syntax for ENTRYPOINT and CMD to make the sample work.

Now I can override the part defined in the CMD part, e.g. as follows

docker run --rm -it my-hello-world-2 "Hello Gabriel"

and indeed it works as expected

The last variant of my Dockerfile is only using the keyword ENTRYPOINT

Again after building the image and running a container we see the expected result

Summary

In this post I have looked a bit deeper into what exactly the ENTRYPOINT and the CMD keywords in a Dockerfile are. It might look like trivial stuff that I have written up here and it certainly is no rocket science. But ofter we overlook the seemingly trivial things and as a consequence come up with solutions that are more complex than necessary or even worse we have a working example and cannot really fully explain”why” it is working.

In the project I am working currently with we have many different containers we deal with and we use them in different environments and context. As a consequence we are heavy users of the variability that ENTRYPOINT and CMD give us.





About List