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.
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
.
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:
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
Links
Docker Documentation - Use the Docker command line
Back to Top