Docker

Docker Commands for Managing Container Lifecycle (Definitive Guide)

Learn the container life cycle concept. Also learn the Docker commands to manage each stage of the lifecycle of the containers.

Debdut Chakraborty
Debdut Chakraborty

Table of Contents

Knowing the various states of the Docker Container is essential for any serious Docker user.

I'll explain the container lifecycle and then show the Docker commands for each stage of the lifecycle.

But before you learn all those things, let's revisit the concept of the container once more.

What are Docker containers, again?

Most traditional definitions of containers go like this:

Containers are a form of operating system virtualization. While traditional hypervisor based virtualization requires separate kernels for separate VMs, containers share the same host kernel, and is therefore, much more lightweight and faster to spin up

The definition will differ from source to source, but the gist is the same.

I find it boring and unnecessarily complicated. I'd like to use something different here to define containers.

Containers are a bunch of processes that are being cheated on.

Why do I say that? Because containers are just a collection of processes, sometimes just a single process. And these processes are being lied to about different resources of its host like networks, process tree, filesystem, hostname etc.

You can run any process, even a shell like bash, and hide your actual process tree from it, give it a different set of networks, hide the actual root filesystem from it, give it a different hostname and essentially, what you'll end up doing is creating a containerized version of the process.

This is achieved by namespaces, separate root filesystem and cgroups.

Docker is just a wrapper around the lower level tools that run and manage the containers, or more specifically saying the container lifecycle.

Apart from that, Docker also does many other things, like making networking for the containers easy, handling storage, pulling & pushing container images etc.

Docker is here to make our life easier.

Now, let's look at the Docker Container Lifecycle

The container lifecycle is basically a series of stages, starting from the creation of the container to its destruction.

The following diagram is going to make this a lot clear.

Docker Container Lifecycle
Container Lifecycle

Let me explain each stage of the container lifecycle.

  • Creation: Many people think that when you run a container, it's one single step. But that's not the case. There is a process which creates the containers first before anything else. More on this later.
  • Started/Running: Once a container is created, it can be started, after which the status changes to Running. This is when the container is actually doing something.
  • Paused: Traditionally to pause a process (which containers basically are), we use the SIGSTOP signal, and to unpause, the SIGCONT signal. But since this signal is observable by the processes, instead of signals, cgroup freezers are used. This way the processes are suspended by freezing the cgroup.
  • Exited/Stopped: The opposite of Running, but not the same as Paused. Stopping a container means sending the SIGTERM signal to the main container process, i.e. PID 1 in the container namespace. It then waits for 10 seconds to let the process exit gracefully. If it doesn't, a SIGKILL signal is sent, and we all know what that means, don't we?
  • Destroyed/Deleted: The container doesn't exist anymore, all the resources that it once allocated, is now gone.

I hope this has made the concept of container lifecycle clearer now. In the following section I'll go through all the specific docker commands that'll help you in managing all these states of containers.

Docker Commands to Manage Container Lifecycle

All the commands (subcommands, if being more specific) that control the container lifecycle belongs to the subcommand container.

In a terminal if you execute docker container --help you'll get a list of subcommands that are associated with multiple operations performable on containers.

But we're not concerned about all of them here. Let's focus only on a specific subset of those.

Container Creation

Container creation is handled by the following commands:

  1. docker container create
  2. docker create

The first one is the long form, while the second one, you guessed it right, is the short form.

I recommend you use the long form as it's more verbose and something I consider a good practice.

What is container creation?

Container creation is creating the R/W layer on top of the specified image's R/O layer[s], and preparing it to run the required program. Creating a container does not start the actual process, you can specify all the configuration parameters like capabilities, CPU limit, memory limit, container image etc right at the creation stage and then start the container anytime you want without having to re-specify those parameters.

Read docker container create --help for a list of configurable options.

Example

First create a container like the following (I used an alpine image here to run the command sleep infinity)

docker container create \
	--name alpine alpine:latest sleep infinity

Once the container is created, you should get the ID of it in the terminal screen. Now you can filter the container list to find the containers that aren't running, but just created.

docker container ls --filter=status=created

Here I'm filtering the list so that I get only the containers that are created. Take a look at the STATUS column, that's what specifies a container's state. You should see something like the following:-

➟ docker container ls --filter=status=created
CONTAINER ID   IMAGE           COMMAND            CREATED          STATUS    PORTS     NAMES
3f8d56fb3f78   alpine:3.13.4   "sleep infinity"   17 seconds ago   Created             alpine

You can also inspect the container and see what state it is in.

➟ docker container inspect -f '{{json .State.Status}}' alpine
"created"

Using docker create will not give you anything different.

Container Startup

To start a container, use one of the following two commands:-

  1. docker container start
  2. docker start

Same thing here, one long and one short version. And I recommend you use the ...container start command.

What is container startup?

Once a container has been created, you can start it. Starting the container means actually running the containerized application.

While creating a container merely prepares all the configuration (how the container will run, runtime environment, runtime constraints, container name etc), it does not allocate any of the resources.

Starting a container allocates the necessary resources and runs the application.

Example

Consider the previous container that we created. Simply start the command with the container name or ID like this:

docker container start alpine

Once the container is started, it's status changes from Created to Running. In the container list, you can filter by the status again like you did previously:-

docker container ls --filter=status=running

On the STATUS column, instead of Running it'll show how long the container has been running since it started.

➟ docker container ls --filter=status=running
CONTAINER ID   IMAGE           COMMAND            CREATED         STATUS          PORTS     NAMES
3f8d56fb3f78   alpine:3.13.4   "sleep infinity"   8 minutes ago   Up 18 seconds             alpine

You can also inspect a container to see it's state. I'll use Go templates to format the output to the only thing that I need, i.e. the state.

docker container inspect \
	-f '{{json .State.Status}}' alpine

You should see something like this -

➟ docker container inspect -f '{{json .State.Status}}' alpine
"running"

Not to sound redundant, but docker start will do just the same thing.

The Special Docker Run Command

This is a special command that ties the container creation and startup together and this is the command that we all use the most.

  1. docker container run
  2. docker run

These commands essentially (i) creates the container and then immediately (ii) starts the same container.

This is useful because creating and leaving a container to start at a later time isn't something we do all too often. When you are at the command line, with the docker CLI, you are here to run a container. And that's exactly what ...container run command does.

Example

Example of this command is very simple. To run a container, instead of docker container create, use docker container run.

You will have to pass all the options you'd pass to docker container create to this docker container run command. This is because docker needs all that information right at that moment since there no more second steps.

➟ docker container run --name echo-me alpine:3.13.4 echo "Subscribe to Linux Handbook"
Subscribe to Linux Handbook
Docker Run vs Start vs Create: Difference Explained
For a docker beginner, terms like docker start, docker run and docker create could be confusing. This article explains the difference with examples.

Container Pausing

Pausing a container means exactly how it sounds. We're not stopping any processes, just pausing it. So if a process inside a container is counting from 1 through 100, and it was paused while it was at count 50 and then unpaused at a later time, the count will resume from 50.

One thing to understand is that while Paused is an actual state, Unpaused is not. When you're unpausing some paused container, you're essentially changing the state from Paused to Running.

The commands that are used to pause a container are as follows:-

  1. docker container pause
  2. docker pause

Similarly you can unpause a container with the unpause counterparts:-

  1. docker container unpause
  2. docker unpause

How a container is paused?

A process is paused by using the SIGSTOP signal, and to unpause, the SIGCONT signal is used. Since these signals can be observed by the processes, instead of signals, cgroup freezers are used. This way the processes are suspended by freezing the cgroup. Freezing a cgroup freezes all of its tasks, and all of its child cgroups.

Example:

For the sake of this demo, I've created a special container. Pull the image by executing the following command:-

docker image pull debdutdeb/pause-demo:v1

You can check the Dockerfile and the code right here.

Once pulled, on one terminal, start a container with this image

docker container run --name pause-demo debdutdeb/pause-demo:v1

It should now show counting starting from 0 and going. Should look something like the following

➟ docker container run --name pause-demo debdutdeb/pause-demo:v1 
Count at 30

On another terminal, pause this container.

docker container pause pause-demo

The moment you're pausing the container, you should see the countdown stop, but the terminal isn't back to you yet. This is because the container isn't yet stopped (I'll talk about this later in this article). Once you unpause this container, countdown should resume, not restart.

docker container unpause pause-demo

To get the terminal back, on a separate terminal run

docker container stop pause-demo

Here is a short video that'll show this in action:-

Before stopping the container you can like the previous ones, check the state of the paused container like so:

➟ docker container inspect -f '{{json .State.Status}}' pause-demo 
"paused"

Container Stopping

You can stop a Docker container using one of the following two commands:-

  1. docker container stop
  2. docker stop

Stopping a container means stopping all the processes associated with it.

This is not the same as pausing a container. If you stop a container, you can restart it, but the processes are not going to resume from the state that they were previously in. A process can save its state information in a "file", and restore its state from that file, but that's a different case.

For example, after you stopped the previous pause-demo container, if you try and restart it using the ...container start command, it'll start counting from the beginning, unlike its pause counterpart which resumes the counting.

How the process works?

When you ask Docker to stop a container, it sends a SIGTERM signal to PID 1 of the container. After that, it waits for 10 seconds, which is its graceful time period, the process is given this time to gracefully exit, in other words clean up and finish whatever it was working. After the 10 seconds is over, Docker sends a final SIGKILL signal, I don't have to tell you what happens next.

Docker sends the signal to PID 1 of the container because every other process is a child of PID 1, if this process gets terminated/killed, the chil processes will automatically cease to exist.

This become clearer with the example below.

Example:

You need to pull an image, again.

docker image pull debdutdeb/stop-demo:v1

For the Dockerfile and source code, check out this gist.

In one terminal, start a container from this image.

docker container run --name stop-demo debdutdeb/stop-demo:v1

Once executed, you should see a screen telling you its PID and that it's waiting.

➟ docker container run --name stop-demo debdutdeb/stop-demo:v1 
My PID in this container is 1
Waiting...

Open another terminal, and execute the stop command on this container.

docker container stop stop-demo

Once run, you should see on the terminal that was running the container, telling you what IT is experiencing, i.e. receiving the SIGTERM signal.

Afterwards it starts counting the seconds, and you'll see after 10 seconds it just stops counting. This isn't because I hard-coded it that way, actually I hard-coded it to go on infinitely.

But after 10 seconds docker sends a SIGKILL signal that we can't catch or ignore, the process is bound to be killed.

You can change the time docker waits before sending the SIGKILL signal by using the -t option with docker container stop.

Once everything is done, you should see something like this

➟ docker container run --name stop-demo debdutdeb/stop-demo:v1 
My PID in this container is 1
Waiting...
SIGTERM ignored,
Starting count..
10 seconds

Here is a small video demonstrating this:-

You can check the state of this container now using ...container inspect like so:-

➟ docker container inspect -f '{{json .State.Status}}' stop-demo
"exited"

Container Deletion

A container is deleted using one of the following two commands:-

  1. docker container rm
  2. docker rm

Container deletion is the opposite of container creation. When you delete a container, it's gone, you can't bring that specific container back. You can start a new one from the same image using the same configuration, make it identical, but it won't be that exact previous one.

Example:

For an example just remove the previous stop-demo container.

docker container rm stop-demo

Until now, all the containers that you created and then stopped can be removed with a single command:-

docker container prune

This will delete all the containers with the status Exited.

Example output:

✗ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
ffbdd621c01d0eb3f42d348eeb75c731ddd9bd85674bb90dece32bd70357e541
21b9ed6a6198cd6ee7e162aebd936ae3e93b3b0f738161924a825a27794f2b20
f5f5149866a6ced675ad3bfff0b7386f75ee3fdbddca86be8b8ba341dba4b27f

Total reclaimed space: 0B

So, you learned the container life cycle concept. And you also learned the Docker commands to manage each stage of the lifecycle of the containers. If you want some more Docker tips, you may learn about these lesser known but useful Docker commands.

3 Docker Commands Advanced Docker Users Should Know
Here are some Docker commands that you might not know about but they could come handy while managing your containers.

I really hope this article has made the theory of container lifecycle easy to understand and also cleared all the commands that you need to manage these different states.

If you have any questions, please leave a comment below.



Join the conversation.