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!
In this blog, we will:
- learn about the different states a container can be in
- learn how to manage them using the
- clear confusions regarding the differences between
- 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.
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.
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:
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…
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.
docker create command, a thin writeable layer is created over the specified Image and prepared to run the main process command (
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
docker create --name app1 nginx:alpine
The id of the new container is printed if successfully created.
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
So, to run app1 we can use –
docker start app1
If you don’t want to explicitly
create and then
start a container, you can do both steps at once using the
For example –
docker run -d --name app2 nginx:alpine
The command above creates and starts a container named
app2 in the background (as specified by
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
Similarly, to get the paused container back to running, we’d use
docker unpause app1
Implementation detail (beyond scope): to implement
freezer cgroup is used instead of POSIX signals.
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:
- Using the
- Using the
- When the main container process has exited/completed
- 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
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
2. Using the
docker kill command
When you run –
docker kill app2
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
-s argument, not just
Here’s an example of issuing the
SIGINT signal –
docker kill -s SIGINT app1
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"
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
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.
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
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
docker rm -f app1
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.
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.
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! 👋