DEV Community

Cover image for The Docker Container Lifecycle | Docker made easy #3
Farhim Ferdous
Farhim Ferdous

Posted on • Updated on

The Docker Container Lifecycle | Docker made easy #3

Link to video: https://www.youtube.com/watch?v=ifvrfriMDaA


Learn how a Container works based on its Lifecycle state and the Docker cli commands to manage them effectively


Have you ever felt overwhelmed by the multitude of docker cli commands?

Have you ever been confused about what's actually happening with your Docker Containers?

If you have, don't worry... you're not alone. 🤗

How a Container behaves depends on which state it is in.

Luckily, once you understand how a Container works based on its Lifecycle state, you will be WAY more confident with Docker.

It will also be much easier for you to switch to more advanced Container management solutions like Docker Compose, Docker Swarm, Kubernetes, or OpenShift, etc.

So, let's learn about the Container Lifecycle, once and for all!

Introduction

In this blog, we will:

  • learn about the different states a container can be in
  • learn how to manage them using the docker cli
  • clear confusions regarding the differences between
    • docker pause and docker stop
    • docker stop and docker kill
  • end with a detailed view of the container lifecycle

We'll also discuss POSIX signals briefly.

If you haven't read the first two blogs on the 'Docker made easy' series, I highly recommend you do so.

If you can't access Medium, try these:

Alright, let's begin by looking at...

The Container Lifecycle

The following diagram shows the states of a simplified Container Lifecycle, which determines how a Container behaves.

simple container lifecycle

simplified container lifecycle state diagram

The text on the arrows corresponds to the docker commands which allow that state transition.

As shown above, a container can be in the following states:

  1. Created
  2. Running
  3. Paused
  4. Stopped
  5. Deleted

Before we dive into each state, we need to know a tiny bit about POSIX signals.

Quick Overview of POSIX signals

Simply put, signals as a standard way that an Operating System (OS) tells a child process how to behave.

There are several signals, each having different purposes. But for us, we'll just focus on the following 2:

  • SIGTERM: this signal is sent to a process to request its termination. It can be caught and interpreted or ignored by the process. This allows the process to perform graceful termination releasing resources and saving state if appropriate.
  • SIGKILL: this signal is sent to a process to cause it to terminate immediately. Unlike SIGTERM, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal (excluding few exceptions).

SIGTERM is usually preferred over SIGKILL since it provides a chance for graceful/safe termination of a process.

If interested, learn more about signals here.


Now let's discuss each state of the container lifecycle one by one...

Created state

This is the first state of the container lifecycle which comes after acquiring or building an Image.

Read the last blog to learn more about how Docker Images work.

Using the docker create command, a thin writeable layer is created over the specified Image and prepared to run the main process command (CMD and/or ENTRYPOINT).

NOTE: On this state, the container is created but not started.

Here's an example of creating a container using the nginx:alpine Image and naming it app1.

docker create --name app1 nginx:alpine
Enter fullscreen mode Exit fullscreen mode

The id of the new container is printed if successfully created.

Running state

As the name suggests, this is the state where the container is actively running. Meaning, the main process has started execution.

For a container that is created (using docker create) or stopped, it can be started using docker start.

So, to run app1 we can use -

docker start app1
Enter fullscreen mode Exit fullscreen mode

If you don't want to explicitly create and then start a container, you can do both steps at once using the docker run.

For example -

docker run -d --name app2 nginx:alpine
Enter fullscreen mode Exit fullscreen mode

The command above creates and starts a container named app2 in the background (as specified by -d).

Paused state

A running container can be paused using the docker pause command. This has the effect of suspending (or freezing) all processes in the specified container.

When paused, the state of the container stays intact - both the disk (file-system) portion and the memory (RAM) portion.

NOTE: A container that is paused is unaware that it has been paused.

So, if we wanted to pause app1, we'd simply do -

docker pause app1
Enter fullscreen mode Exit fullscreen mode

Similarly, to get the paused container back to running, we'd use unpause -

docker unpause app1
Enter fullscreen mode Exit fullscreen mode

Implementation detail (beyond scope): to implement pause, the freezer cgroup is used instead of POSIX signals.

Stopped state

A container that is stopped doesn't have its main processes actively running (I know, Duh 🤷‍♀ī¸).

When stopped, the disk portion of the state is persisted i.e. saved.

But unlike when paused, the memory portion of the state is cleared when a container is stopped. This is the main difference between the paused and stopped states.

A container can be stopped in 4 primary ways:

  1. Using the docker stop command
  2. Using the docker kill command
  3. When the main container process has exited/completed
  4. When 'Out Of Memory Exception' (OOME) is encountered

Let's discuss each individually -

1. Using the docker stop command

This command is as simple as -

docker stop app1
Enter fullscreen mode Exit fullscreen mode

When executed, the main container process receives a SIGTERM signal (by default) and after a grace period (default 10s as of writing), it receives a SIGKILL signal.

2. Using the docker kill command

When you run -

docker kill app2
Enter fullscreen mode Exit fullscreen mode

the SIGKILL signal is directly sent to the main container process (default behavior).

This means the difference between docker stop and docker kill is that - stop can allow safe termination (within the grace period) while kill terminates immediately.

For this reason, stop is preferred over kill. Review 'Quick Overview of POSIX signals' above if you're confused.

It's important to note that, docker kill can also be used to issue any signal to the container process using the —signal/-s argument, not just SIGKILL.

Here's an example of issuing the SIGINT signal -

docker kill -s SIGINT app1
Enter fullscreen mode Exit fullscreen mode

3. When the main process has exited/completed

A container can stop automatically if its main process has exited/completed.

This can happen if

  • the main process runs into an exception/interrupt
  • or if the task completes after a certain point in time (instead of running in an infinite loop like a server)

Here's a very simple example -

docker run alpine echo "hi"
Enter fullscreen mode Exit fullscreen mode

This command runs the echo "hi" command inside an alpine container, prints "hi" to the console, and exits immediately after the echo command is done.

4. When 'Out Of Memory Exception' (OOME) is encountered

If your containers attempt to use more memory than the system has available, you may experience an Out Of Memory Exception (OOME). As a result, some containers or even the Docker daemon might be killed by the kernel OOM killer.

Therefore, you should ensure that your application runs on hosts with adequate memory in production.

The simplest way of managing memory is by limiting the maximum amount of memory a container can use using the -m option -

docker run -m 50m redis:alpine
Enter fullscreen mode Exit fullscreen mode

This runs a container from the redis:alpine Image with a memory limit of 50MB.

For learning more about managing memory and mitigating risks associated with OOME, refer to the official documentation.

Deleted state

A container that is in the created state or stopped can be removed with docker rm. This will result in the removal of all data associated with the container like the processes, file system, volume & network mappings, etc.

If app1 is stopped, for example, you can remove it like -

docker rm app1
Enter fullscreen mode Exit fullscreen mode

If however the container is running or paused and you attempt to remove it with docker rm, you will get an error response from the daemon saying the container needs to be stopped.

If you are sure that your running container doesn't have any associated data which needs proper cleanup, then you can perform force removal using -f -

docker rm -f app1
Enter fullscreen mode Exit fullscreen mode

But it is recommended not to do this, especially if the data integrity of the container is important - like the case for databases.

Advanced Container Lifecycle

Here is a detailed diagram of the Container Lifecycle we just discussed including events in rectangles, courtesy of Docker Saigon.

advanced container lifecycle

This should be easier to understand if you have read the preceding discussion.

Or do you think I should have started with this instead of the simplified one? Interested to hear what you think.

Conclusion

Today we learnt about the different states a Container can be in - the Container Lifecycle, as well as how to use the docker cli to maneuver through those states.

Once we know the basics of Containers, we can do really powerful stuff with more advanced tools like Docker Compose, Docker Swarm, Kubernetes, or OpenShift, etc.

Containers are awesome, and we're just getting started!

On the next docker blog, we'll discuss how Volumes work on Docker.

Till then, be bold and keep learning.

But most importantly...

Tech Care! 👋

Top comments (0)