07 Practical Exercises With Docker Play and Learn

07 Practical Exercises with Docker Play and Learn #

Hello, I’m Chrono.

With today’s lesson, we are concluding our “Getting Started” section. With this foundation of container knowledge, we will soon begin learning Kubernetes in earnest. But before that, let’s review and practice what we have learned so far to further solidify our understanding.

I want to remind you that there is a wealth of Docker-related content out there. In this introductory section, I have selected some of the most basic and useful information to share with you. In my opinion, we don’t need to fully understand all of Docker’s functionalities, and I don’t recommend delving too deep into its internal architecture details and specific command-line parameters. It’s too time-consuming. Just knowing enough to use Docker effectively and being able to consult the official documentation when needed is sufficient.

After all, the goal of our course is Kubernetes, and Docker is just one of the many well-known container runtimes out there. Of course, if your current work is closely tied to Docker, that’s a different story.

Now, let me summarize container technology briefly, and then demonstrate two practical projects: deploying a Registry and WordPress using Docker.

Review of Container Technology Key Points #

Container technology is a significant innovation in the backend application domain. It completely transforms the way applications are developed, delivered, and deployed, and it is the foundation of “cloud-native” (lecture 01).

Containers are based on Linux’s underlying features such as namespaces, cgroups, and chroot. Although these features have been around for a long time, containers did not gain widespread attention until Docker came along and integrated them together, gradually becoming well-known among developers (lecture 02).

There are three core concepts in container technology: Container, Image, and Registry (lecture 03).

Image

Essentially, containers are a form of virtualization technology, similar to virtual machines (VMs). Both can separate system resources and isolate application processes, but containers are more lightweight and have higher runtime efficiency. They are therefore more suitable for cloud computing requirements.

An image is the static form of a container. It packages the application, along with the necessary operating system, configuration files, environment variables, and so on. As a result, it can run on any system, eliminating many deployment, maintenance, and platform migration issues.

The image is composed of multiple layers, where each layer is a set of files. The layers utilize Union FS technology to merge into a file system that the container can use. The benefit of this fine-grained structure is that identical layers can be shared and reused, saving costs in disk storage and network transmission. It also simplifies the process of building images (lecture 04).

To facilitate image management, image registries were introduced. They provide a centralized storage for various containerized applications, allowing users to upload and download images freely. It is the ideal way to distribute images (lecture 05).

Currently, the most well-known public image registry is Docker Hub. Other examples include quay.io and gcr.io. These websites offer many high-quality images that can be integrated into our own application systems.

Container technology has various specific implementations, with Docker being the initial and most popular one. Docker’s primary form is the “Docker Engine” that runs on Linux. The docker command we use in our daily work is just a frontend tool. It needs to communicate with the Docker daemon backend service to achieve its functionalities.

Common commands for managing containers include docker ps, docker run, docker exec, docker stop, and so on. Common commands for managing images include docker images, docker rmi, docker build, docker tag, and so on. Common commands for managing image registries include docker pull, docker push, and so on.

With this simple review of container technology, let’s now apply the knowledge we have learned in the “Getting Started” section and start practicing with Docker.

Setting up a private image registry #

In the fifth lesson, we mentioned that in an offline environment, we can set up our own private registry. However, since the registry is a network service, we needed to learn about container networking before we could set up a private registry.

There are many existing solutions for private image registries, but today I will only choose the simplest one, Docker Registry. The more feature-rich CNCF Harbor will be introduced in the future when we study Kubernetes.

You can search for “registry” on the Docker Hub website to find its official page (https://registry.hub.docker.com/_/registry/):

Image

The Docker Registry website provides detailed instructions, including download commands and usage. We can follow these instructions completely.

First, you need to pull the image using the docker pull command:

docker pull registry

Next, we need to do a port mapping to expose the port to the outside, so that Docker Registry can provide its services. The container’s internal port is 5000, so for simplicity, let’s use the same port 5000 outside. Therefore, the command to run is docker run -d -p 5000:5000 registry:

docker run -d -p 5000:5000 registry

After starting the Docker Registry, you can use docker ps to check its running status. You can see that it indeed maps the host’s port 5000 to the container’s port 5000.

Image

Next, we need to use the docker tag command to tag the image and upload it. Since the target of the upload is not the default Docker Hub, but the local private registry, we must prepend the repository’s address (domain name or IP address) to the image’s name. The format is similar to an HTTP URL.

For example, here I have changed “nginx:alpine” to “127.0.0.1:5000/nginx:alpine”:

docker tag nginx:alpine 127.0.0.1:5000/nginx:alpine

Now, the image has a complete name with an additional repository address prefix, and we can push it using the docker push command:

docker push 127.0.0.1:5000/nginx:alpine

Image

To verify if the push was successful, we can delete the tagged image and then download it again:

docker rmi 127.0.0.1:5000/nginx:alpine
docker pull 127.0.0.1:5000/nginx:alpine

Image

Here, docker pull indeed completed the image download task, but since the original layers already existed, there was no actual download action, only a new image tag was created.

Although Docker Registry does not have a graphical interface, it provides a RESTful API that can be used to view the images in the repository. The specific endpoint information can be found in the official documentation (https://docs.docker.com/registry/spec/api/). The following two curl commands respectively retrieve the image list and the tag list for the Nginx image:

curl 127.1:5000/v2/_catalog
curl 127.1:5000/v2/nginx/tags/list

Image

As we can see, because the application is encapsulated in the image, we only need one or two simple commands to set up a private registry. There is no need for complex software installation, environment setup, debugging, testing, and other tedious operations. This was unimaginable before the advent of container technology.

Setting up a WordPress website #

The Docker Registry application is relatively simple, as it only requires a single container to run a complete service. Now let’s set up a more complex WordPress website.

The website will require three containers: WordPress, MariaDB, and Nginx. These are all very popular open-source projects with official images available on the Docker Hub website. The instructions on the webpage are also very detailed, so I will skip the specific search process and directly use the docker pull command to pull their images:

docker pull wordpress:5
docker pull mariadb:10
docker pull nginx:alpine

I have drawn a simple network architecture diagram so you can visually understand the relationships between them:

Image

This system can be considered a typical website. MariaDB acts as the backend relational database with port number 3306. WordPress is the middle application server that uses MariaDB to store data and uses port 80. Nginx acts as the front-end reverse proxy, exposing port 80 to the outside and forwarding requests to WordPress.

Let’s start by running MariaDB. According to the documentation, we need to configure several environment variables like “MARIADB_DATABASE”. We can use the --env parameter to specify the database, username, and password when starting the container. Here, I specify the database as “db”, the username as “wp”, the password as “123”, and the root password as “123” as well.

Below is the docker run command to start MariaDB:

docker run -d --rm \
    --env MARIADB_DATABASE=db \
    --env MARIADB_USER=wp \
    --env MARIADB_PASSWORD=123 \
    --env MARIADB_ROOT_PASSWORD=123 \
    mariadb:10

After starting it, we can use the docker exec command to execute the database client tool “mysql” and verify if the database is running correctly:

docker exec -it 9ac mysql -u wp -p

After entering the username “wp” and password “123” we set earlier, we have successfully connected to MariaDB and can use commands like show databases; and show tables; to view the contents of the database. Of course, it should be empty at the moment.

Image

Since the default subnet of Docker’s bridge network mode is “172.17.0.0/16” and the host is always “172.17.0.1”, with IP addresses assigned sequentially, if there were no other running containers before, the IP address of the MariaDB container should be “172.17.0.2”. This can be verified using the docker inspect command:

docker inspect 9ac |grep IPAddress

Image

Now that the database service is up and running, it’s time to run the WordPress application server. It also needs to specify some environment variables using the --env parameter in order to connect to MariaDB. Note that “WORDPRESS_DB_HOST” must be the IP address of MariaDB, otherwise, it won’t be able to connect to the database.

docker run -d --rm \
    --env WORDPRESS_DB_HOST=172.17.0.2 \
    --env WORDPRESS_DB_USER=wp \
    --env WORDPRESS_DB_PASSWORD=123 \
    --env WORDPRESS_DB_NAME=db \
    wordpress:5

Since the WordPress container doesn’t use the -p parameter to map port numbers when starting, it cannot be accessed directly from the outside. We need to configure an Nginx reverse proxy in front, which will forward requests to the WordPress port 80.

To configure the Nginx reverse proxy, we need to know the IP address of WordPress. We can again use the docker inspect command to check, and if everything goes as expected, it should be “172.17.0.3”. We can then write the following configuration file (Nginx usage can be referred to other resources; I won’t go into detail here):

server {
  listen 80;
  default_type text/html;

  location / {
      proxy_http_version 1.1;
      proxy_set_header Host $host;
      proxy_pass http://172.17.0.3;
  }
}

With this configuration file in place, the most crucial step is to map the host’s port to Nginx’s internal port 80 using the -p parameter. We also mount the configuration file to Nginx’s “conf.d” directory using the -v parameter. This way, Nginx will use the pre-configured file to listen for HTTP requests on port 80 and forward them to the WordPress application.

docker run -d --rm \
    -p 80:80 \
    -v `pwd`/wp.conf:/etc/nginx/conf.d/default.conf \
    nginx:alpine

Once all three containers are running, we can use docker ps to check their status:

Image

As you can see, while WordPress and MariaDB use ports 80 and 3306, respectively, these ports are isolated within the containers and not visible to the outside world. Only Nginx has a port mapping and can send and receive data on port 80, which aligns with our network diagram.

Now the entire system is running in the container environment. Let’s open a browser and enter “127.0.0.1” or the IP address of the virtual machine (in my case, “http://192.168.10.208”) to see the WordPress interface:

Image

After creating basic user accounts and initializing the website, we can log in to MariaDB again and see if there is any data:

Image

As you can see, WordPress has created many tables in the database, which proves that we have successfully set up the containerized WordPress website.

Summary #

Alright, today we briefly reviewed container technology. Here is a mind map summarizing the key points of all the container knowledge we covered. You can use it for revision.

Image

We also used Docker to build two services in practice: a Registry image repository and a WordPress website.

Through these two projects, you should be able to feel the tremendous changes that containerization brings to backend development. It simplifies the packaging, distribution, and deployment of applications. With just a few simple commands, tasks that previously required a lot of scripts to complete can now be done easily. It is definitely a “blessing” for both development and operations.

However, while experiencing the convenience of containers, have you noticed some shortcomings? For example:

  • We still need to manually run some commands to start the application and then manually confirm its running state.
  • Running applications composed of multiple containers is cumbersome and requires human intervention (such as checking IP addresses) to maintain network communication.
  • Existing network mode functionality is only suitable for a single machine. How do we run applications on multiple servers and achieve load balancing?
  • What if we need to increase the number of applications? Container technology is of no help in this case.

In fact, if we carefully organize these docker run commands used to run containers, write them as scripts, and then use some Shell or Python programming to automate them, we may be able to obtain a viable solution.

This solution goes beyond container technology itself. It plans the running order of containers, network connections, and data persistence at a higher level, which is the embryonic form of “container orchestration.” This is also the main focus of Kubernetes, which we will learn about later.

Homework #

Finally, it is time for homework. I have two questions for you to think about:

  1. After completing the “Introduction” section, what deeper thoughts and understanding do you have regarding container technology compared to when you first started?
  2. What aspects of problem-solving do you think container orchestration should address?

Feel free to leave comments and engage in discussions. If you find this helpful, please share it with your friends for learning together.

The next class will be a video class where I will demonstrate the operations we have learned so far. See you in the next class! -