03 Containerized Apps Once You Know These, You Are a Docker Expert

03 Containerized Apps Once You Know These, You Are a Docker Expert #

Hello, I’m Chrono.

In the previous lesson, we learned the most fundamental concept in container technology: containers. We know that a container is a specially isolated environment within a system, where processes can run without interference. We can simplify this description even further: containers are isolated processes.

Compared to cumbersome virtual machines, containers have many advantages. So how do we create and run containers? Do we need to use the triple combination of Linux kernel’s namespaces, cgroups, and chroot?

Of course not. That approach is too primitive. Therefore, today, we will take Docker as an example to learn about containerized applications and how to manipulate them.

What is Containerized Application? #

When we run containers, we don’t start from scratch. Instead, we first need to pull an “image” and then use this “image” to start the container, like in [the first lesson]:

docker pull busybox      
docker run busybox echo hello world

So, what exactly is this “image”? And what is its relationship with the “container”?

In fact, we have also encountered the term “image” in other occasions, such as the most common CD/DVD image, the hard disk image used for computer reinstallation, and virtual machine system images. These “images” have some similarities: they are read-only, not allowed to be modified, and store a series of files in a standard format, which can be extracted and run when needed.

The same principle applies to images in container technology. Since containers are dynamically created by the operating system, it is possible to solidify its initial environment in a certain way, save it as a static file, and “flatten” the container. This makes it very convenient for storage, transmission, and version management.

If we continue to use the analogy of the “small board house” mentioned before, then the image can be said to be a “model room” that integrates all the information necessary for the running process, such as the file system, dependency libraries, environment variables, and startup parameters. After that, no matter where the image file is placed, the operating system can quickly rebuild the container based on this “model room”, and the application program will see a consistent operating environment.

In terms of functionality, images are similar to common installation packages such as tar, rpm, and deb. However, the biggest difference is that images not only package the basic executable files, but also the entire system environment required for application runtime. This gives images excellent cross-platform portability and compatibility, allowing developers to develop on one system (such as Ubuntu), package it into an image, and run it on another system (such as CentOS) without worrying about environment dependencies. It is a more advanced way of application packaging.

With this understanding, let’s take a look at the Docker commands used in the first lesson.

docker pull busybox pulls an image that packages the busybox application, including the busybox program and its required runtime environment.

docker run busybox echo hello world extracts various information from the image, creates an isolated environment using namespace, cgroup, and chroot technologies, and then runs the echo command of busybox to output the string “hello world”.

Because these two steps are based on standard Linux system calls and read-only image files, the results obtained are identical regardless of the operating system or the container implementation technology used.

By extension, any application can be packaged and distributed to run in this manner. This is also the ultimate goal of “Build once, Run anywhere” that countless developers dream of. Therefore, the term “containerized application” refers to the fact that the application no longer interacts directly with the operating system, but is encapsulated into an image and then run in a container environment.

By now, you should know that an image is a static application container, while a container is a dynamic application image. They depend on each other and are inseparable.

Do you still remember the Docker official architecture diagram shown earlier? We briefly introduced it in the first lesson. As you can see, the core objects in Docker are images and containers:

Image

Alright, now that we understand what a containerized application is, let’s move on to learn how to manipulate containerized applications. Since an image is the foundation of a container, we will first take a look at some commonly used commands related to images.

What are the common image operations? #

In the previous lessons, you should already be familiar with two basic commands, docker pull to pull images from a remote repository, and docker images to list the images currently available locally.

The usage of docker pull is relatively simple and similar to regular downloading. However, we need to know the naming rules for images in order to accurately obtain the container image we want.

The full name of an image consists of two parts, the name and the tag, connected by a colon (:).

The name indicates the identity of the application, such as busybox, Alpine, Nginx, Redis, etc. The tag is a additional label used to differentiate different versions of the application. It can be any string, such as 3.15 as a pure numeric version number, jammy as a project code, 1.21-alpine as a version number with an operating system name, etc. There is a special tag called “latest,” which is the default tag. If only the name is provided without a tag, the default “latest” tag will be used.

Now, you can combine the name and the tag to pull some images using docker pull:

docker pull alpine:3.15
docker pull ubuntu:jammy
docker pull nginx:1.21-alpine
docker pull nginx:alpine
docker pull redis

After obtaining these images, let’s use the docker images command to see their specific information:

image

In this list, you can see that the REPOSITORY column represents the name of the image, the TAG represents the tag of the image, and what does the third column “IMAGE ID” mean?

It can be regarded as the unique identifier of the image, just like an identity card number. For example, we can use “ubuntu:jammy” to represent the Ubuntu 22.04 image, and we can also use its ID “d4c2c…” to represent it.

Furthermore, you may notice that the IMAGE IDs of the two images “nginx:1.21-alpine” and “nginx:alpine” in the screenshot are the same, both are “a63aa…”. This is also easy to understand. It’s like a person’s identity card number is unique but can have a full name, a given name, a nickname, or an alias. The same image can have different tags, making it easier to understand in different scenarios.

IMAGE ID also has an advantage. Since it is in hexadecimal form and unique, Docker provides a “shortcut” operation for it. When using the image locally, we don’t need to write the entire long string of numbers like the name. Usually, writing the first three digits is enough to locate it quickly. When the number of images is small, it may even be possible to use two or even one digit.

Let’s look at another image operation command, docker rmi, which is used to delete unused images and can save disk space. Pay attention to the command rmi, which is actually an abbreviation for “remove image”.

Now let’s try using the name and IMAGE ID to delete images:

docker rmi redis
docker rmi d4c

The first rmi deletes the Redis image because no explicit tag is provided, and the default tag “latest” is used. The second rmi does not provide a name but directly uses the first three digits of the IMAGE ID, which is “d4c”. Docker will directly find the image with this ID prefix and delete it.

There are many other commands in Docker related to images, but the above docker pull, docker images, and docker rmi are the most commonly used three. We will introduce other commands in subsequent lessons.

image

Common Container Operations #

Now that we have stored the images locally, we can use the docker run command to run these static applications and turn them into dynamic containers.

The basic format is “docker run set parameters”, followed by the “image name or ID,” and there may be additional “run commands” afterwards.

For example, this command:

docker run -h srv alpine hostname

Here, -h srv is the container’s runtime parameter, alpine is the image name, and hostname after it represents the “hostname” program to be run in the container, which outputs the hostname.

docker run is the most complex container operation command with a lot of additional parameters to adjust the container’s runtime status. You can use --help to see its help information. Today, I will only mention a few of the most commonly used parameters.

-it indicates opening an interactive shell, which allows you to directly enter the container, just like logging into a virtual machine. (It is actually a combination of the -i and -t parameters)

-d indicates running the container in the background, which is very useful when starting server programs like Nginx and Redis.

--name can give the container a name for easy viewing, but it is not necessary. If you don’t use this parameter, Docker will assign a random name.

Now let’s practice these three parameters to run Nginx, Redis, and Ubuntu respectively:

docker run -d nginx:alpine            # Run Nginx in the background
docker run -d --name red_srv redis    # Run Redis in the background
docker run -it --name ubuntu 2e6 sh   # Use the IMAGE ID to log in to Ubuntu18.04

Because the third command uses -it instead of -d, it will enter the Ubuntu system in the container. We need to open another terminal window and use the docker ps command to view the container’s running status:

Image

As you can see, each container also has a “CONTAINER ID,” which functions the same as the “IMAGE ID” of the image and uniquely identifies the container.

For a running container, we can use the docker exec command to execute another program inside it. It works similar to docker run, but because the container already exists, it won’t create a new container. Its most common usage is to open a shell using the -it parameter to enter the container, for example:

docker exec -it red_srv sh

This way, we “log in” to the Redis container and can easily view the service’s runtime status or logs.

A running container can also be forcibly stopped using the docker stop command. Here, we can still use the container name, or perhaps using the first three digits of the “CONTAINER ID” might be more convenient.

docker stop ed4 d60 45c

After a container is stopped, it cannot be seen using the docker ps command. However, the container is not completely destroyed. We can use the docker ps -a command to view all containers in the system, including those that have stopped running:

Image

These stopped containers can be restarted using the docker start command. If you are sure that you no longer need them, you can use the docker rm command to permanently delete them.

Note that this command is very similar to docker rmi, the difference being that it does not have the letter “i” afterward, so it only deletes the container and not the image.

Now let’s run the docker rm command to delete these containers using the first two digits of the “CONTAINER ID”:

docker rm ed d6 45

After executing the delete command, using docker ps -a to view the list, you will find that these containers have completely disappeared.

This container management method may seem complicated, as you need to run ps to view the IDs and then delete them. If you’re not careful, the system may accumulate many “dead” containers, occupying system resources. Is there a way to automatically delete unnecessary containers in Docker?

Of course, there is. It’s by adding a --rm parameter when executing the docker run command. This tells Docker not to save the container and to automatically clean up as soon as it finishes running, saving us the trouble of manually managing containers.

Let’s experiment with Nginx, Redis, and Ubuntu again. Add the --rm parameter (omitting the name parameter):

docker run -d --rm nginx:alpine 
docker run -d --rm redis
docker run -it --rm 2e6 sh 

Then use docker stop to stop the containers, and using docker ps -a, you will find that you don’t need to manually execute docker rm anymore. Docker has automatically deleted these three containers.

Image

Summary #

Today, we have learned about containerized applications and used Docker to work with images and containers. We ran containerized applications such as Alpine, Nginx, and Redis.

An image is a static representation of a container that packages all the runtime dependencies of an application, making it easy to save and distribute. By using container technology to run images, we create dynamic containers. Since images are read-only and cannot be modified, the runtime environment of the application remains consistent.

Containerized applications refer to applications packaged as images and launched as containers in a container environment.

Due to the numerous commands in Docker, each with many parameters, it is difficult to cover them all in one lesson. I encourage you to refer to Docker’s built-in help or official documentation (https://docs.docker.com/reference/) for more information and practice. I believe you can become a Docker expert with additional practice.

Now, let’s summarize the image and container operations we covered today:

  1. Common image operations include docker pull, docker images, and docker rmi. These commands are used for pulling, viewing, and deleting images, respectively.
  2. The most commonly used command for starting containers is docker run, which has many parameters to adjust the container’s runtime state. For running background services, use -d.
  3. The docker exec command allows us to execute any program inside a container, making it especially useful for debugging and troubleshooting.
  4. Other common container operations include docker ps, docker stop, and docker rm, used for viewing containers, stopping containers, and deleting containers, respectively.

Homework #

Finally, it’s time for homework. Here are two questions for you to ponder:

  1. Can you explain your understanding of container images? What are the differences and pros/cons between container images, RPM, and DEB installation packages?
  2. What are the differences between docker run and docker exec, and how should you use them?

Feel free to participate in the discussion in the comments section. It is said that typing out what you have learned can significantly improve your understanding.

See you in the next lesson. -