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:
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 maintainer=sathsara@mydomain.com CMD ["echo","Hello World"]
This Dockerfile
uses the latest ubuntu
images as the parent image. Then, the LABEL
directive is used to specify sathsara@mydomain.com
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 maintainer=sathsara@mydomain.com  ---> 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 maintainer=sathsara@mydomain.com  ---> 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 maintainer=sathsara@mydomain.com  ---> 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:
- 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 followingDockerfile
created in Exercise 2.01: Creating Our First Dockerfile:# This is my first Docker image FROM ubuntu LABEL maintainer=sathsara@mydomain.com RUN apt-get update CMD ["The Docker Workshop"] ENTRYPOINT ["echo", "You are reading"]
- 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 aswelcome: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: - 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:
- 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
and2.0
of thewelcome
images are built on your computer. - Execute the
docker container run
command to start a new container from the Docker image that you built instep 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 theENTRYPOINT
directive, andThe Docker Workshop
comes from the parameter provided with theCMD
directive. - 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 theYou are reading
argument provided in theENTRYPOINT
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
.