Docker Image Layering

This article covers the operations involved with layering images in Docker.


Introduction

So far, we’ve covered the basics of creating containers and pulling Docker images. Now we will look at how we can customize our Docker images. By customizing a Docker image, we have the chance to pre-install and configure our software so the container is ready for use when deployed.


Get and Run the Base Image

We will start by pulling down a version of httpd just as we did before.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ docker pull httpd:2.4

2.4: Pulling from library/httpd
5eb5b503b376: Pull complete
a43a76ccc967: Pull complete
942bd346e7f7: Pull complete
cdb155854ae6: Pull complete
10c4d45228bf: Pull complete
Digest: sha256:5cc947a200524a822883dc6ce6456d852d7c5629ab177dfbf7e38c1b4a647705
Status: Downloaded newer image for httpd:2.4
docker.io/library/httpd:2.4

By specifying httpd:2.4, we pinned the version. I recommend doing this as opposed to using the :latest version where applicable. This way, you can always be sure to grab a stable version you’ve tested against your code.

Now let’s run the image and create a new container called web_image

1
2
3
$ docker run --name web_image -d httpd:2.4

6f733c08704229c6d5c099f01316ed37b3074243ce6555929cafaae078e7d3fa

We can check the status of our newly built container by running the docker ps command.

1
2
3
4
$ docker ps

CONTAINER ID   IMAGE       COMMAND              CREATED          STATUS          PORTS     NAMES
6f733c087042   httpd:2.4   "httpd-foreground"   21 seconds ago   Up 20 seconds   80/tcp    web_image

Now we can run the docker images command to gather some details about the images docker currently knows about:

1
2
3
4
5
6
7

$ docker images

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
httpd        2.4       a8ea074f4566   4 weeks ago   144MB


Please take note of the docker image size; it will be helpful information as we continue.


Install Tools and Code in the Container

Now that our container is running, we need to install the tools and copy the website code into the container. We use the docker exec command to run bash interactively, which allows us to interact with the running container.

1
2
3
$ docker exec -it web_image bash

root@6f733c087042:/usr/local/apache2#

Once connected, we will update the container package list and install git.

1
2
3
root@6f733c087042:/usr/local/apache2# apt update && apt install git -y
...
done.

Now that we have git available, we can clone the website repository content.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@6f733c087042:/usr/local/apache2# git clone  https://github.com/jlambert229/html5up-solid-state.git /tmp/html5up-solid-state

Cloning into '/tmp/html5up-solid-state'...
remote: Enumerating objects: 85, done.
remote: Counting objects: 100% (85/85), done.
remote: Compressing objects: 100% (80/80), done.
remote: Total 85 (delta 3), reused 85 (delta 3), pack-reused 0
Receiving objects: 100% (85/85), 1.38 MiB | 7.05 MiB/s, done.
Resolving deltas: 100% (3/3), done.
root@6f733c087042:/usr/local/apache2#

After cloning the repository, we should verify the contents by running ls.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@6f733c087042:/usr/local/apache2# ls -l /tmp/html5up-solid-state

total 68
-rw-r--r-- 1 root root 17065 Feb 24 07:02 LICENSE.txt
-rw-r--r-- 1 root root   901 Feb 24 07:02 README.md
drwxr-xr-x 6 root root  4096 Feb 24 07:02 assets
-rw-r--r-- 1 root root 19087 Feb 24 07:02 elements.html
-rw-r--r-- 1 root root  5997 Feb 24 07:02 generic.html
drwxr-xr-x 2 root root  4096 Feb 24 07:02 images
-rw-r--r-- 1 root root  7671 Feb 24 07:02 index.html

Apache will automatically serve the htdocs directory; we will swap the htdocs contents with our cloned repository.

1
2
3
4
root@6f733c087042:/usr/local/apache2# ls -l htdocs/

total 4
-rw-r--r-- 1 504 staff 45 Jun 11  2007 index.html

We will remove the default index.html file and copy the cloned repository.

1
2
3
root@6f733c087042:/usr/local/apache2# rm htdocs/index.html
root@6f733c087042:/usr/local/apache2# cp -r /tmp/html5up-solid-state/* htdocs/
root@6f733c087042:/usr/local/apache2#

We can confirm that the data has been copied successfully by quickly running an ls command on the htdocs/ folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@6f733c087042:/usr/local/apache2# ls -l htdocs/

total 68
-rw-r--r-- 1 root root 17065 Feb 24 07:04 LICENSE.txt
-rw-r--r-- 1 root root   901 Feb 24 07:04 README.md
drwxr-xr-x 6 root root  4096 Feb 24 07:04 assets
-rw-r--r-- 1 root root 19087 Feb 24 07:04 elements.html
-rw-r--r-- 1 root root  5997 Feb 24 07:04 generic.html
drwxr-xr-x 2 root root  4096 Feb 24 07:04 images
-rw-r--r-- 1 root root  7671 Feb 24 07:04 index.html

We finished interacting with this container, so go ahead and exit.

1
2
3
root@6f733c087042:/usr/local/apache2# exit

exit

Create an Image from the Container

Now we are going to create an image layer from the running container. A new image layer will allow us to create new containers with all of the work we’ve done pre-built into the image.

First, we will need to copy the container id from Docker. We can quickly do this by running docker ps.

1
docker ps

Now we will use the docker commit command to take our running container state and save it as a docker image. We will also be specifying some details about the docker image. we use the example/web_image as the image label, and :v1 as a tag.

1
2
3
$ docker commit 6f733c087042 example/web_image:v1

sha256:ad1a7ce03f5b6317b8120e7804bb0ac70243cf5440cf8ace7a198363303d312d

We can confirm our image was created by running the docker images command again. You might notice that the image size is different from the original httpd image.

1
2
3
4
5
$ docker images

REPOSITORY          TAG       IMAGE ID       CREATED         SIZE
example/web_image   v1        ad1a7ce03f5b   2 minutes ago   251MB
httpd               2.4       a8ea074f4566   4 weeks ago     144MB

Clean up the Template for a Second Version

Now we will need to clean up our template. Log in to the container with docker exec again.

1
2
3
$ docker exec -it web_image bash

root@6f733c087042:/usr/local/apache2#

Remove the cloned code from the /tmp/ directory:

1
2
3
4
rm -rf /tmp/html5up-solid-state/

root@6f733c087042:/usr/local/apache2#

Let’s confirm the contents of /tmp/ are gone with the ls command.

1
2
3
root@6f733c087042:/usr/local/apache2# ls -a /tmp
.  ..

We will use apt to remove git and clean the cache.

1
2
3
4
5
6
7
8
root@6f733c087042:/usr/local/apache2# apt remove git -y && apt autoremove -y && apt clean

...
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Removing ...
root@6f733c087042:/usr/local/apache2#

Exit the container:

1
exit

we will confirm the status of the running container again with docker ps

1
2
3
4
$ docker ps

CONTAINER ID   IMAGE       COMMAND              CREATED       STATUS       PORTS     NAMES
6f733c087042   httpd:2.4   "httpd-foreground"   7 hours ago   Up 7 hours   80/tcp    web_image

Now we will create an image from the updated container with docker commit, labelling it v2 Create an image from the updated container:

1
2
3
$ docker commit 6f733c087042 example/web_image:v2

sha256:0ae12ea5ded7a697c5db4770964ac4a82f77bb82384a40a12d6f651d39ede405

We can run docker images to confirm that we have successfully created a new image. Verify that both images are now running:

1
2
3
4
5
6
$ docker images

REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
example/web_image   v2        0ae12ea5ded7   38 seconds ago   168MB
example/web_image   v1        ad1a7ce03f5b   14 minutes ago   251MB
httpd               2.4       a8ea074f4566   4 weeks ago      144MB

We can see now the v2 version of our image has been created, and that the size is substantially smaller than our v1. now we are ready to delete our v1 image. we can remove images with the docker rmi command. This command removes and untags images from the docker host. Delete the v1 image:

1
2
3
4
$ docker rmi example/web_image:v1
Untagged: example/web_image:v1
Deleted: sha256:ad1a7ce03f5b6317b8120e7804bb0ac70243cf5440cf8ace7a198363303d312d
Deleted: sha256:0feec3a81ecdd55772e82e005f77370247f825a8b85c3d84738f3562d24f3515

Run Multiple Containers from the Image

Now that our new image is ready for use, we can test it by running multiple web server containers from the same image.

Run multiple containers using the new image:

Note

I am using the logical AND operator && to combine the individual docker run commands, this is not a requirement.

1
2
3
4
5
6
$ docker run -d --name web1 -p 8081:80 example/web_image:v2 && docker run -d --name web2 -p 8082:80
example/web_image:v2 && docker run -d --name web3 -p 8083:80 example/web_image:v2

2c0394bac748ca36b8a556102d9d0653c9fed5337aabf588ca1bd3a8dfe2ffe0
10082d29527613a61b4deba55892b4e78c5fcf814ad430a1be3cae6bc566f800
6c1ede5cb1f9f6aaeeeaa44de12d7aaf78f1423f69d5299e94a6b20dc12c1947

Check the status of the containers with the docker ps command again.

1
2
3
4
5
6
7
$ docker ps

CONTAINER ID   IMAGE                  COMMAND              CREATED          STATUS          PORTS                                   NAMES
6c1ede5cb1f9   example/web_image:v2   "httpd-foreground"   47 seconds ago   Up 46 seconds   0.0.0.0:8083->80/tcp, :::8083->80/tcp   web3
10082d295276   example/web_image:v2   "httpd-foreground"   47 seconds ago   Up 46 seconds   0.0.0.0:8082->80/tcp, :::8082->80/tcp   web2
2c0394bac748   example/web_image:v2   "httpd-foreground"   48 seconds ago   Up 47 seconds   0.0.0.0:8081->80/tcp, :::8081->80/tcp   web1
6f733c087042   httpd:2.4              "httpd-foreground"   7 hours ago      Up 7 hours      80/tcp                                  web_image

Lets go ahead and Stop the base web_image image, and then run docker ps again.

1
2
3
4
5
6
7
$ docker stop web_image && docker ps

web_image
CONTAINER ID   IMAGE                  COMMAND              CREATED         STATUS         PORTS                                   NAMES
6c1ede5cb1f9   example/web_image:v2   "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:8083->80/tcp, :::8083->80/tcp   web3
10082d295276   example/web_image:v2   "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:8082->80/tcp, :::8082->80/tcp   web2
2c0394bac748   example/web_image:v2   "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:8081->80/tcp, :::8081->80/tcp   web1


Testing Our Containers

Using a web browser, or by using curl, we can verify that the containers are running successfully: I am combining curl and grep to quickly check the HTTP status codes for the containers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ curl -I 10.1.110.49:8081 | grep HTTP

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0  7671    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
HTTP/1.1 200 OK

$ curl -I 10.1.110.49:8082 | grep HTTP

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0  7671    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
HTTP/1.1 200 OK

$ curl -I 10.1.110.49:8083 | grep HTTP

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0  7671    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
HTTP/1.1 200 OK

Seeing the 200 OK status codes lets me know that the server responded correctly.

As a final check, we can confirm the images list has changed with docker images

1
2
3
4
5
6

$ docker images
REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
example/web_image   v2        0ae12ea5ded7   20 minutes ago   168MB
httpd               2.4       a8ea074f4566   4 weeks ago      144MB


Visualizing the Layering

We can see the difference in the layering of the images by using docker history on both images:

note: I’ve omitted some of history output and focused the input on the relevant area.

First we will look at the httpd:2.4 image history.

Take note of the image id.

1
2
3
4
5
6
7
8
9
$ docker history httpd:2.4

IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
a8ea074f4566   4 weeks ago   /bin/sh -c #(nop)  CMD ["httpd-foreground"]     0B
<missing>      4 weeks ago   /bin/sh -c #(nop)  EXPOSE 80                    0B
<missing>      4 weeks ago   /bin/sh -c #(nop) COPY file:c432ff61c4993ecd…   138B
<missing>      4 weeks ago   /bin/sh -c #(nop)  STOPSIGNAL SIGWINCH          0B
<missing>      4 weeks ago   /bin/sh -c set -eux;   savedAptMark="$(apt-m…   60.5MB


Now we will compare to example/web_image:v2 image history. You can see that the httpd:2.4 image is consumed as a layer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ docker history example/web_image:v2

IMAGE          CREATED             CREATED BY                                      SIZE      COMMENT
0ae12ea5ded7   About an hour ago   httpd-foreground                                24.3MB
a8ea074f4566   4 weeks ago         /bin/sh -c #(nop)  CMD ["httpd-foreground"]     0B
<missing>      4 weeks ago         /bin/sh -c #(nop)  EXPOSE 80                    0B
<missing>      4 weeks ago         /bin/sh -c #(nop) COPY file:c432ff61c4993ecd…   138B
<missing>      4 weeks ago         /bin/sh -c #(nop)  STOPSIGNAL SIGWINCH          0B
<missing>      4 weeks ago         /bin/sh -c set -eux;   savedAptMark="$(apt-m…   60.5MB


Closing Thoughts

Docker image layering can be deceptively simple. Under the hood however, we can see that there are many complex layers of commands supporting our containers. To summarize what we did: We covered several docker operations including creating, updating, and destroying docker images. We also reviewed the layers and demonstrated the relationships between images. If you want to learn more about docker images and docker commands, I’d suggest reviewing the links to the docker documentation listed below.

Acknowledgements

This article was heavily influenced by the docker training available at acloudguru .

I highly recommend their training course Learn Docker by Doing


Docker Documentation - Use the Docker command line


Back to Top