Basic Container Lifecycle Management

Throughout your container journey, you will be pulling, starting, stopping, and removing containers from your local environment quite frequently. Prior to deploying a container in a production environment, it is critical to run the container locally to understand how it should work. This includes starting, stopping and restarting containers, getting details about how the container is running, and, of course accessing verbose logs to view critical details about the applications running inside the container. The basic commands we are going to discuss are the following:

  • docker start: This command starts a container instance that is no longer in a running state.
  • docker stop: This command stops a running container instance.
  • docker restart: This command restarts a running container.
  • docker pull: This command downloads a container image to the local cache.
  • docker attach: This command allows users to gain access (or attach) to the primary process of a running Docker container instance.
  • docker exec: This command executes a command inside a running container.
  • docker rm: This command deletes a stopped container.
  • docker rmi: This command deletes a container image.
  • docker inspect: This command shows verbose details about the state of a container.

Container life cycle management is an essential component of managing containers in production environments. Knowing how to investigate running containers is a critical skill that will help you evaluate the health of your containerized infrastructure.

In the following exercise, we are going to manage a container using these commands.

Managing Container Life Cycles

When managing containers in any kind of environment, it is important to understand the status of container instances. Most of the time we use base container images that contain a specific baseline configuration on top of which the applications are deployed. Ubuntu is one of the most commonly used base images that are used for packaging applications.

Unlike a full operating system image, the Ubuntu base container image is quite slim and intentionally leaves out a lot of packages. Most of the base images do have a package system that allows us to install any missing package.

Keep in mind, though, that you want to keep the base images as slim as possible, only installing the packages you really need. This ensures that container images can quickly be pulled and started by Docker hosts.

In this exercise, we will work with the official Ubuntu base container image. This image will be used to start container instances on which we will use various container life cycle management commands.

In a new terminal or PowerShell window, execute the docker pull command to download Ubuntu 18.04 container image. Remember, we can use tags to specify the image version. If you don't provide a tag, the latest will automatically be pulled.

docker pull ubuntu:18.04

You should see the following output:

18.04: Pulling from library/ubuntu
7c457f213c76: Pull complete
Digest: sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2bb2e43c98
Status: Downloaded newer image for ubuntu:18.04
docker.io/library/ubuntu:18.04

Use the docker pull command to download the Ubuntu 19.04 base image:

docker pull ubuntu:19.04

You should see the following output:

19.04: Pulling from library/ubuntu
4dc9c2fff018: Pull complete
0a4ccbb24215: Pull complete
c0f243bc6706: Pull complete
5ff1eaecba77: Pull complete
Digest: sha256:2adeae829bf27a3399a0e7db8ae38d5adb89bcaf1bbef378240bc0e6724e8344
Status: Downloaded newer image for ubuntu:19.04
docker.io/library/ubuntu:19.04

Use the docker images command to confirm that the container images are downloaded to the local container cache:

docker images

The contents of the local container cache will display the Ubuntu 18.04 and Ubuntu 19.04 base images, as well as any other images you have in your local cache:

REPOSITORY   TAG     IMAGE ID      CREATED        SIZE
ubuntu       18.04   f9a80a55f492  12 months ago  63.2MB
hello-world  latest  d2c94e258dcb  13 months ago  13.3kB
ubuntu       19.04   c88ac1f841b7   4 years ago   70MB
...          ...     ...            ...           ...

Before running these images, use the docker inspect command to get verbose output about the images. In your terminal, run the docker inspect command and use the IMAGE ID value if the Ubuntu 18.04 container image as an argument:

docker inspect f9a80a55f492

The inspect output will contain a large list of attributes that define the container. For example, you can see what environment variables are configured within the container, whether the container has a hostname set when the image was last updated, and a breakdown of all of the layers that define that container.

"Id": "sha256:f9a80a55f492e823bf5d51f1bd5f87ea3eed1cb31788686aa99a2fb61a27af6a",
    "RepoTags": [
        "ubuntu:18.04"
    ],
    "RepoDigests": [        "ubuntu@sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2bb2e43c98"
    ],
    "Parent": "",
    "Comment": "",
    "Created": "2023-05-30T09:32:09.432301537Z",
    "Container":"00da56b63e7a5e6508d4ff7a380a7fb2b4e7ffcb5dcf799d41cb75bf20f12132",

Inspecting the Ubuntu 19.04 container, you can see that this parameter is different. Run the docker inspect command in the Ubuntu 19.04 container image ID:

docker inspect c88ac1f841b7

In the displayed output, you will see that this container image was created on a different date to the 18.04 container image:

"Id": "sha256:c88ac1f841b72add46f5a8b0e77c2ad6864d47e5603686ea64375acd55e27906",
"RepoTags": [
    "ubuntu:19.04"
],
"RepoDigests": [         "ubuntu@sha256:2adeae829bf27a3399a0e7db8ae38d5adb89bcaf1bbef378240bc0e6724e8344"
],
"Parent": "",
"Comment": "",
"Created": "2020-01-16T01:20:46.938732934Z",
"Container": "1d952a25729fba44399443aa7cb60e2452250fc4535b7135db02424006e304d5"

This can be useful if, for example, you know that a security vulnerability might be present in an Ubuntu base image.

After inspecting both the container images, it will be clear that our best choice in this scenario is to stick with the LTS 18.04 release. The preceding outputs show that the 18.04 release is more up to date than the 19.04. This is something to be expected, as Ubuntu generally will provide more stable updates for the LTS releases.

Write the docker run command with the -d flag on your terminal of choice to start up an instance of the Ubuntu 18.04 container:

docker run -d ubuntu:18.04

This time we are using the -d flag. This tells Docker to run the container in daemon mode (or in the background). If we omit the -d flag, the container will take over our current terminal session until the primary process inside the container terminates.

Check the status of the container using the docker ps -a command:

docker ps -a

This will reveal a similar output to the following:

CONTAINER ID  IMAGE         COMMAND      CREATED         STATUS                PORTS  NAMES
f05ee6d22795  ubuntu:18.04  "/bin/bash"  2 minutes ago   Exited (0) 2 minutes ago         condescending_thompson

The container is stopped and exited. This is because the primary process inside this Ubuntu container is /bin/bash, which is a shell. The Bash shell cannot run without being executed in an interactive mode since it expects text input from the user.

Run the docker run command again, passing in the -i flag to make the session interactive, and the -t flag to allocate a pseudo-tty handler to the container. A pseudo-tty handler will essentially link the user's terminal to the interactive Bash sell running inside the container.

This will allow Bash to run properly since it will instruct the container to run in an interactive mode, expecting user input. You can also give the container a human-readable name by passing in the --name flag. Use the following command in your terminal:

docker run -i -t -d --name ubuntu18 ubuntu:18.04

You should now see the new instance running, as well as the instance that failed to start previously:

CONTAINER ID   IMAGE                                                  COMMAND                  CREATED          STATUS                      PORTS     NAMES
0b4b857747a5   ubuntu:18.04                                           "/bin/bash"              21 seconds ago   Up 20 seconds                         ubuntu18
f05ee6d22795   ubuntu:18.04

We now have an Ubuntu container up and running. We can run commands inside the container using the docker exec command. We can use the exec command to access a Bash shell, which will allow us to run commands inside the container. Similar to docker run, pass in the -i and -t flags to make it an interactive session. Also pass in the name or ID of the container, so that Docker knows which container you are targeting. The final argument of docker exec is always the command you wish to execute. In this case, it will be /bin/bash to start a Bash shell inside the container instance:

docker exec -it ubuntu18 /bin/bash

You should immediately see your terminal to change to a root shell. This indicates that you have successfully launched a shell inside your Ubuntu container. The hostname of the container, (in my case 0b4b857747a5) is taken from the first twelve characters of the container ID. This allows the user to know for certain which container they are accessing

root@0b4b857747a5:/#

Run the echo command inside the ubuntu18 container instance to write a hello world message:

echo "Hello world from ubuntu18" > helloworld.txt

Run the exit command to exit from the Bash shell of the ubuntu18 container. You should return to your normal terminal shell:

root@0b4b857747a5:/# exit

Now, lets create a second container named ubuntu19 that will also run in the Docker environment using the Ubuntu 19.04 image:

docker run -itd --name ubuntu19 ubuntu:19.04

Again, run the docker exec to access a shell of this second container. Remember to use the name or container ID of the new container you created. Likewise, access a Bash shell inside this container, so the final argument will be /bin/bash:

docker exec -it ubuntu19 /bin/bash

You should see that the prompt once again changed to a Bash root shell, similar to how it did for the Ubuntu 18.04 container image:

root@b073985d739a:/#

Run the echo command inside the ubuntu19 container instance to write a hello world message:

echo "Hello world from ubuntu19" > helloworld.txt

Currently, you should have two Ubuntu container instances running in your Docker environment, with two separate hello-world files in the home directory. If you run the docker ps command, you should see your containers

CONTAINER ID   IMAGE                                                  COMMAND                  CREATED          STATUS                     PORTS     NAMES
b073985d739a   ubuntu:19.04                                           "/bin/bash"              11 minutes ago   Up 11 minutes                        ubuntu19
0b4b857747a5   ubuntu:18.04                                           "/bin/bash"              3 hours ago      Up 3 hours                           ubuntu18

Instead of using docker exec to access a shell inside our containers, we are going to use it to display the output of the helloworld.txt files, by executing the cat command inside the containers

docker exec -it ubuntu18 cat helloworld.txt

The output will display the helloworld message we added to the container in previous steps. Notice that as soon as the cat command was completed and the output displayed, the user was moved back to the context of the main terminal. This is because the docker exec session will only exist for as long the command provided by the user is running.

In the previous example of the Bash shell, Bash will only exit if the user terminates it by using the exit command. In this example, only the Hello world output is displayed, because the cat command displayed the output and exited, and thus, terminating the docker exec session.

Hello world from ubuntu 18

Run the same cat command in the ubuntu2 container instance:

Hello world from ubuntu 19

As you can see, Docker was able to allocate an interactive session on both the containers, execute the command, and return the output directly in our running container instances.

In a similar manner to that we used to execute commands inside our running containers, we can also stop, start and restart them. Stop one of your container instances using the docker stop command. In your terminal session, execute the docker stop command, followed by the name or container ID of the ubuntu19 container:

docker stop ubuntu19

This command should return the name of the container.

We can use the docker ps command to view all running container instances:

docker ps

The output will display the ubuntu1 container up and running:

CONTAINER ID   IMAGE          COMMAND       CREATED      STATUS         PORTS     NAMES
c8c0010f65fb   ubuntu:18.04   "/bin/bash"   5 days ago   Up 4 seconds             ubuntu18

Execute the docker ps -a command to view all container instances, regardless of whether they are running. This will show us the stopped container:

docker ps -a

The output should display something similar to the following:

CONTAINER ID   IMAGE                                                  COMMAND                  CREATED       STATUS                     PORTS     NAMES
df6354e844f9   ubuntu:19.04                                           "/bin/bash"              5 days ago    Exited (0) 5 days ago                ubuntu19
c8c0010f65fb   ubuntu:18.04                                           "/bin/bash"              5 days ago    Up 2 minutes                         ubuntu18

From this state we can experiment with starting, stopping, or executing commands inside the containers.

When container instances are in a stopped state, we can use the docker rm command to delete the container instances altogether. Use the docker rm followed by the name or id of the container to delete the ubuntu19 instance:

docker rm ubuntu19

The output should be the name of the container

To completely reset the state of your docker environment, delete the base images you downloaded during this post. Use the docker images command to view the cached based images:

docker images

A list of Docker images and associated metadata in the local cache should be displayed:

REPOSITORY                                      TAG       IMAGE ID       CREATED         SIZE
ubuntu                                          18.04     f9a80a55f492   12 months ago   63.2MB
hello-world                                     latest    d2c94e258dcb   13 months ago   13.3kB
ubuntu                                          19.04     c88ac1f841b7   4 years ago     70MB

Use the docker rmi command followed by an image ID to delete the first image:

docker rmi f9a80a55f492

Similar to docker pull, the rmi command will delete each image and all associated layers

Untagged: ubuntu:18.04
Untagged: ubuntu@sha256:152dc042452c496007f07ca9127571cb9c29697f42acbfad72324b2bb2e43c98
Deleted: sha256:f9a80a55f492e823bf5d51f1bd5f87ea3eed1cb31788686aa99a2fb61a27af6a
Deleted: sha256:548a79621a426b4eb077c926eabac5a8620c454fb230640253e1b44dc7dd7562

It is important to periodically clean up your Docker environment, as building and running containers can cause large amounts of disk usage over time. To streamline the cleaning up of your environment, Docker provides a prune command that will automatically remove old containers and base images

docker system prune -fa

Executing this command will remove any container images that are not tied to an existing running container, along with any other resources in your Docker environment.

Summary

Using commands such as docker run, docker start, docker exec,
docker ps, and docker stop, we have explored the basics of container life cycle management through the Docker CLI. Through the various steps in this post, we launched container instances from the same base image, configured them using docker exec, and cleaned up the deployments using other basic container life cycle commands such as docker rm and docker rmi.

Source: Kostas Kalafatis