Book Image

The Docker Workshop

By : Vincent Sesto, Onur Yılmaz, Sathsara Sarathchandra, Aric Renzo, Engy Fouda
5 (1)
Book Image

The Docker Workshop

5 (1)
By: Vincent Sesto, Onur Yılmaz, Sathsara Sarathchandra, Aric Renzo, Engy Fouda

Overview of this book

No doubt Docker Containers are the future of highly-scalable software systems and have cost and runtime efficient supporting infrastructure. But learning it might look complex as it comes with many technicalities. This is where The Docker Workshop will help you. Through this workshop, you’ll quickly learn how to work with containers and Docker with the help of practical activities.? The workshop starts with Docker containers, enabling you to understand how it works. You’ll run third party Docker images and also create your own images using Dockerfiles and multi-stage Dockerfiles. Next, you’ll create environments for Docker images, and expedite your deployment and testing process with Continuous Integration. Moving ahead, you’ll tap into interesting topics and learn how to implement production-ready environments using Docker Swarm. You’ll also apply best practices to secure Docker images and to ensure that production environments are running at maximum capacity. Towards the end, you’ll gather skills to successfully move Docker from development to testing, and then into production. While doing so, you’ll learn how to troubleshoot issues, clear up resource bottlenecks and optimize the performance of services. By the end of this workshop, you’ll be able to utilize Docker containers in real-world use cases.
Table of Contents (17 chapters)
Preface

Building Docker Images

In the last section, we learned how to create a Dockerfile. The next step of the process is to build a Docker image using the Dockerfile.

A Docker image is the template used to build Docker containers. This is analogous to how a house plan can be used to create multiple houses from the same design. If you are familiar with object-oriented programming concepts, a Docker image and a Docker container have the same relationship as a class and an object. A class in object-oriented programming can be used to create multiple objects.

A Docker image is a binary file consisting of multiple layers based on the instructions provided in the Dockerfile. These layers are stacked on top of one another, and each layer is dependent on the previous layer. Each of the layers is a result of the changes from the layer below it. All the layers of the Docker image are read-only. Once we create a Docker container from a Docker image, a new writable layer will be created on top of other read-only layers, which will contain all the modifications made to the container filesystem:

Figure 2.2: Docker image layers

Figure 2.2: Docker image layers

As illustrated in the preceding image, the docker image build command will create a Docker image from the Dockerfile. The layers of the Docker image will be mapped to the directives provided in the Dockerfile.

This image build process is initiated by the Docker CLI and executed by the Docker daemon. To generate a Docker image, the Docker daemon needs access to the Dockerfile, source code (for example, index.html), and other files (for example, properties files) that are referenced in the Dockerfile. These files are typically stored in a directory that is known as the build context. This context will be specified while executing the docker image build command. The entire context will be sent to the Docker daemon during the image build process.

The docker image build command takes the following format:

$ docker image build <context>

We can execute the docker image build command from the folder that contains the Dockerfile and the other files, as shown in the following example. Note that the dot (.) at the end of the command is used to denote the current directory:

$ docker image build.

Let's see the Docker image build process for the following sample Dockerfile:

FROM ubuntu:latest
LABEL [email protected]
CMD ["echo","Hello World"]

This Dockerfile uses the latest ubuntu images as the parent image. Then, the LABEL directive is used to specify [email protected] as the maintainer. Finally, the CMD directive is used to echo "Hello World" as the output of the image.

Once we execute the docker image build command for the preceding Dockerfile, we can see an output similar to the following on the console during the build process:

Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:latest
latest: Pulling from library/ubuntu
2746a4a261c9: Pull complete 
4c1d20cdee96: Pull complete 
0d3160e1d0de: Pull complete 
c8e37668deea: Pull complete
Digest: sha256:250cc6f3f3ffc5cdaa9d8f4946ac79821aafb4d3afc93928
        f0de9336eba21aa4
Status: Downloaded newer image for ubuntu:latest
 ---> 549b9b86cb8d
Step 2/3 : LABEL [email protected]
 ---> Running in a4a11e5e7c27
Removing intermediate container a4a11e5e7c27
 ---> e3add5272e35
Step 3/3 : CMD ["echo","Hello World"]
 ---> Running in aad8a56fcdc5
Removing intermediate container aad8a56fcdc5
 ---> dc3d4fd77861
Successfully built dc3d4fd77861

The first line of the output is Sending build context to Docker daemon, which indicates that the building starts by sending the build context to the Docker daemon. All the files available in the context will be sent recursively to the Docker daemon (unless specifically asked to ignore certain files).

Next, there are steps mentioned as Step 1/3 and Step 2/3, which correspond to the instructions in the Dockerfile. As the first step, the Docker daemon will download the parent image. In the preceding output shown, Pulling from library/ubuntu indicates this. For each line of the Dockerfile, a new intermediate container will be created to execute the directive, and once this step is completed, this intermediate container will be removed. The lines Running in a4a11e5e7c27 and Removing intermediate container a4a11e5e7c27 are used to indicate this. Finally, the Successfully built dc3d4fd77861 line is printed when the build is completed without any errors. This line prints the ID of the newly built Docker image.

Now, we can list the available Docker images using the docker image list command:

$ docker image list

This list contains the locally built Docker images and Docker images pulled from remote Docker repositories:

REPOSITORY   TAG       IMAGE ID        CREATED          SIZE
<none>       <none>    dc3d4fd77861    3 minutes ago    64.2MB
ubuntu       latest    549b9b86cb8d    5 days ago       64.2MB

As shown in the preceding output, we can see two Docker images. The first Docker image with the IMAGE ID of dc3d4fd77861 is the locally built Docker image during the build process. We can see that this IMAGE ID is identical to the ID in the last line of the docker image build command. The next image is the ubuntu image that we used as the parent image of our custom image.

Now, let's build the Docker image again using the docker image build command:

$ docker image build
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM ubuntu:latest
 ---> 549b9b86cb8d
Step 2/3 : LABEL [email protected]
 ---> Using cache
 ---> e3add5272e35
Step 3/3 : CMD ["echo","Hello World"]
 ---> Using cache
 ---> dc3d4fd77861
Successfully built dc3d4fd77861

This time, the image build process was instantaneous. The reason for this is the cache. Since we did not change any content of the Dockerfile, the Docker daemon took advantage of the cache and reused the existing layers from the local image cache to accelerate the build process. We can see that the cache was used this time with the Using cache lines available in the preceding output.

The Docker daemon will perform a validation step before starting the build process to make sure that the Dockerfile provided is syntactically correct. In the case of an invalid syntax, the build process will fail with an error message from the Docker daemon:

$ docker image build
Sending build context to Docker daemon  2.048kB
Error response from daemon: Dockerfile parse error line 5: 
unknown instruction: INVALID

Now, let's revisit the locally available Docker images with the docker image list command:

$ docker image list

The command should return the following output:

REPOSITORY    TAG       IMAGE ID         CREATED          SIZE
<none>        <none>    dc3d4fd77861     3 minutes ago    64.2MB
ubuntu        latest    549b9b86cb8d     5 days ago       64.2MB

Note that there was no name for our custom Docker image. This was because we did not specify any repository or tag during the build process. We can tag an existing image with the docker image tag command.

Let's tag our image with IMAGE ID dc3d4fd77861 as my-tagged-image:v1.0:

$ docker image tag dc3d4fd77861 my-tagged-image:v1.0

Now, if we list our images again, we can see the Docker image name and the tag under the REPOSITORY and TAG columns:

REPOSITORY        TAG       IMAGE ID        CREATED         SIZE
my-tagged-image   v1.0      dc3d4fd77861    20 minutes ago  64.2MB
ubuntu            latest    549b9b86cb8d    5 days ago      64.2MB

We can also tag an image during the build process by specifying the -t flag:

$ docker image build -t my-tagged-image:v2.0 .

The preceding command will print the following output:

Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM ubuntu:latest
 ---> 549b9b86cb8d
Step 2/3 : LABEL [email protected]
 ---> Using cache
 ---> e3add5272e35
Step 3/3 : CMD ["echo","Hello World"]
 ---> Using cache
 ---> dc3d4fd77861
Successfully built dc3d4fd77861
Successfully tagged my-tagged-image:v2.0

This time, in addition to the Successfully built dc3d4fd77861 line, we can see a Successfully tagged my-tagged-image:v2.0 line, which indicates the tagging on our Docker image.

In this section, we learned how to build a Docker image from a Dockerfile. We discussed the difference between a Dockerfile and a Docker image. Then, we discussed how a Docker image is made up of multiple layers. We also experienced how caching can accelerate the build process. Finally, we tagged the Docker images.

In the next exercise, we are going to build a Docker image from the Dockerfile that we created in Exercise 2.01: Creating Our First Dockerfile.

Exercise 2.02: Creating Our First Docker Image

In this exercise, you will build the Docker image from the Dockerfile that you created in Exercise 2.01: Creating Our First Dockerfile and run a Docker container from the newly built image. First, you will run the Docker image without passing any arguments, expecting You are reading The Docker Workshop as the output. Next, you will run the Docker image with Docker Beginner's Guide as the argument and expect You are reading Docker Beginner's Guide as the output:

  1. First, make sure you are in the custom-docker-image directory created in Exercise 2.01: Creating Our First Dockerfile. Confirm that the directory contains the following Dockerfile created in Exercise 2.01: Creating Our First Dockerfile:
    # This is my first Docker image
    FROM ubuntu 
    LABEL [email protected] 
    RUN apt-get update
    CMD ["The Docker Workshop"]
    ENTRYPOINT ["echo", "You are reading"]
  2. Build the Docker image with the docker image build command. This command has the optional -t flag to specify the tag of the image. Tag your image as welcome:1.0:
    $ docker image build -t welcome:1.0 .

    Note

    Do not forget the dot (.) at the end of the preceding command, which is used to denote the current directory as the build context.

    It can be seen from the following output that all five steps mentioned in the Dockerfile are executed during the build process. The last two lines of the output suggest that the image is successfully built and tagged:

    Figure 2.3: Building the welcome:1.0 Docker image

    Figure 2.3: Building the welcome:1.0 Docker image

  3. Build this image again without changing the Dockerfile content:
    $ docker image build -t welcome:2.0 .

    Note that this build process completed much quicker than the previous process due to the cache being used:

    Figure 2.4: Building the welcome:1.0 Docker image using the cache

    Figure 2.4: Building the welcome:1.0 Docker image using the cache

  4. Use the docker image list command to list all the Docker images available on your computer:
    $ docker image list

    These images are available on your computer, either when you pull them from a Docker registry, or when you build on your computer:

    REPOSITORY   TAG      IMAGE ID        CREATED          SIZE
    welcome      1.0      98f571a42e5c    23 minutes ago   91.9MB
    welcome      2.0      98f571a42e5c    23 minutes ago   91.9MB
    ubuntu       latest   549b9b86cb8d    2 weeks ago      64.2MB

    As you can see from the preceding output, there are three Docker images available. The ubuntu image is pulled from the Docker Hub, and version (tag) 1.0 and 2.0 of the welcome images are built on your computer.

  5. Execute the docker container run command to start a new container from the Docker image that you built in step 1 (welcome:1.0):
    $ docker container run welcome:1.0

    The output should be as follows:

    You are reading The Docker Workshop

    You receive the expected output of You are reading The Docker Workshop. You are reading is due to the parameter provided with the ENTRYPOINT directive, and The Docker Workshop comes from the parameter provided with the CMD directive.

  6. Finally, execute the docker container run command again, this time with command-line arguments:
    $ docker container run welcome:1.0 "Docker Beginner's Guide"

    You will get the output You are reading Docker Beginner's Guide because of the command-line argument, Docker Beginner's Guide, and the You are reading argument provided in the ENTRYPOINT directive:

    You are reading Docker Beginner's Guide

In this exercise, we learned how to build a custom Docker image using the Dockerfile and run a Docker container from the image. In the next section, we are going to learn other Docker directives that we can use in the Dockerfile.