Docker Series #3: Container Lifecycle and Restart Policies

Welcome to the third part of our Docker series. We started with the basics, understanding what Docker is and how to run a simple container with an interactive Bash shell. Then, we escalated things by showing you how to quickly spin up a web server using apache httpd.

If you're new to Docker or missed our earlier posts, I highly recommend checking them out. The foundational knowledge we've built there will certainly help you as we dive into today's topic, Container Lifecycle and Restart Policies.

Docker Series #1: Hello Docker
As always, my goal here is to explain what Docker is using plain language and relatable examples, I hope to give you a clear understanding of what Docker is.
Docker Series #2: Running a Webserver (httpd)
Imagine a situation where you need to quickly set up a web server for testing. Perhaps you want to verify a NAT policy by initiating traffic from the internet or examine

Docker's ability to manage containers' lifecycles and restart them as needed is one of its most powerful features. It provides reliability, scalability, and helps you maintain your services running smoothly.

Container Lifecycle

So far we've discovered Docker's ability to create isolated environments. But what about the persistence of data? Let's dive into an example that showcases how data is preserved across the life cycle of a Docker container.

Imagine you're working inside an Ubuntu container, and you create a file called testfile with some content. You then exit the container (stopping the container) but don't delete it. What happens to the file?

1. First, We Create a Container

PS C:\Users\vsurr> docker run --name ubuntu_test -it ubuntu:latest bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
3153aa388d02: Pull complete
Digest: sha256:0bced47fffa3361afa981854fcabcd4577cd43cebbb808cea2b1f33a3dd7f508
Status: Downloaded newer image for ubuntu:latest

This command will create a new Ubuntu container and place us inside a bash shell within the container. If the Ubuntu image is not found locally, Docker will download it.

2. Next, We Make a File Inside the Container

root@5e01e4cd7ac7:/# echo "This is a test file" > testfile

root@5e01e4cd7ac7:/# cat testfile
This is a test file
root@5e01e4cd7ac7:/#

We just created a file named testfile with the content This is a test file

3. Exiting Without Killing the Container

In Docker, you can detach from the container and leave it running by using the escape sequence CTRL-p CTRL-q.

  1. Press CTRL-p (hold the CTRL key and press the p key)
  2. Then immediately press CTRL-q (hold the CTRL key and press the q key).

After pressing this sequence, you'll be returned to your host command prompt, and the container will continue to run in the background.

4. Stopping the Container

PS C:\Users\vsurr> docker stop ubuntu_test
ubuntu_test

We've now stopped our container, everything remains exactly how we left it (hopefully, we will see)

5. Checking Containers

PS C:\Users\vsurr> docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

PS C:\Users\vsurr> docker ps -a
CONTAINER ID   IMAGE           COMMAND   CREATED         STATUS                        PORTS     NAMES
5e01e4cd7ac7   ubuntu:latest   "bash"    2 minutes ago   Exited (137) 53 seconds ago             ubuntu_test

We can see our stopped container but there are no running containers.

6. Starting the Container Again

PS C:\Users\vsurr> docker start ubuntu_test
ubuntu_test

7. Reconnecting to the Container

PS C:\Users\vsurr> docker exec -it ubuntu_test bash
root@5e01e4cd7ac7:/#

8. Verifying Our File is Still There

root@5e01e4cd7ac7:/# cat testfile
This is a test file

Our file is still there, with the content intact. So what's happening here? The magic lies in how Docker handles the filesystem of a container. When you stop a container, its filesystem remains preserved. Anything you create or modify will still be there when you restart it. It's like stopping and starting a virtual machine.

If you stop and then remove the container using the command docker rm ubuntu_test, all the data within the container, including our test file, will be gone. This removal deletes the container and all associated files. Once a container is removed, everything inside it is erased, and you start fresh the next time. If you're working with vital data, be mindful of this behaviour, and consider using volumes or other mechanisms for essential persistent storage. We will cover Docker Volumes in the upcoming posts.

Container Restart Policies

Container restart policies determine how Docker should treat containers when they exit. Managing the lifecycle of containers is crucial, and Docker provides several options to control this behaviour.

  1. always - Always restart the container if it stops, regardless of the reason.
  2. unless-stopped - Restart the container unless it has been explicitly stopped by the user.
  3. on-failure - Restart the container if it exits due to an error (a non-zero exit status)

You can set the restart policy directly in the command line when you run a container (or via a Dockerfile), here is the syntax

docker container run --restart=<policy>
💡
By default, Docker containers are configured with the no restart policy. This means that if a container exits for any reason, whether due to an error or a normal termination, it will not be automatically restarted by Docker.

always

This policy always restarts a failed container unless it's been explicitly stopped. This is valuable for critical services that must remain running. When the Docker daemon restarts, the container with this policy will be restarted (even if you have manually stopped the container). For example, if you stop the container with docker stop and then restart the docker daemon, the container will be restarted.

To demonstrate this, I'm going to spin up a container with the always policy and attach my terminal to its shell. When I type exit, the container's main process which is bash exists so the container also stops. But since we have the always policy, docker just restarts the container again.

PS C:\Users\vsurr> docker run --name ubuntu_test --restart=always -it ubuntu bash
root@0a57bfa8eb7d:/# [I'm inside the container for like 10 seconds]
root@0a57bfa8eb7d:/# exit
exit
PS C:\Users\vsurr> docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED          STATUS         PORTS     NAMES
0a57bfa8eb7d   ubuntu    "bash"    17 seconds ago   Up 2 seconds             ubuntu_test
PS C:\Users\vsurr>

unless-stopped

This policy will restart the container unless it has been explicitly stopped by the user. This means if you restart the Docker daemon or the host machine, the container will be restarted, but it won't be restarted if you manually stop it.

Let me spin up a new web-server, stop it manually and then restart docker. With the unless-stopped policy, the web-server will not be restarted.

PS C:\Users\vsurr> docker run -d --name web --restart=unless-stopped -p 8080:80 httpd
5bd33af2ddf204ce04cafe1cd50efbe0c6fcedfbcdde7b692a6a8d77e23ce7dd

PS C:\Users\vsurr> docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED         STATUS         PORTS                  NAMES
5bd33af2ddf2   httpd     "httpd-foreground"   4 seconds ago   Up 2 seconds   0.0.0.0:8080->80/tcp   web

PS C:\Users\vsurr> docker stop web
web
PS C:\Users\vsurr> docker ps -a
CONTAINER ID   IMAGE     COMMAND              CREATED         STATUS                     PORTS     NAMES
5bd33af2ddf2   httpd     "httpd-foreground"   4 minutes ago   Exited (0) 3 minutes ago             web

As you can see the container remains stopped.

on-failure

With on-failure policy, the container will be restarted if it exits with a non-zero exit code, indicating an error or abnormal termination. In computer programming, a non-zero exit code usually signifies that the program has terminated because of an error. Optionally, you can limit the number of times the Docker daemon attempts to restart the container using the :max-retries option.

PS C:\Users\vsurr> docker run -d --name failure-demo --restart=on-failure ubuntu bash -c 'sleep 10; exit 1;'
e50952d916842463855578f3455c86cb1a7b50ea551d144802dbcd8bb817bc48

The shell command bash -c 'sleep 10; exit 1;' within the container is designed to cause a failure by exiting with a status of 1 after a 10-second pause. As a result, the container will be restarted by Docker according to the on-failure policy (every 10 seconds)

PS C:\Users\vsurr> docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS         PORTS     NAMES
e50952d91684   ubuntu    "bash -c 'sleep 10; …"   About a minute ago   Up 9 seconds             failure-demo
PS C:\Users\vsurr>
PS C:\Users\vsurr> docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS         PORTS     NAMES
e50952d91684   ubuntu    "bash -c 'sleep 10; …"   About a minute ago   Up 2 seconds             failure-demo
💡
The important thing to note is that with the on-failure policy, if you manually stop the container and restart docker, the container will be restarted. 

By selecting the appropriate policy, you can ensure that your containers behave in alignment with your specific use cases and system requirements.