Docker Commands for Managing Container Lifecycle (Definitive Guide)
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.
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 asPaused
. 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, aSIGKILL
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:
docker container create
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:-
docker container start
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.
docker container run
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
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:-
docker container pause
docker pause
Similarly you can unpause a container with the unpause counterparts:-
docker container unpause
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:-
docker container stop
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:-
docker container rm
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.
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.