21 Days of Docker-Day 8-Docker Images, Layers & Containers

Welcome to Day 8 of 21 days of Docker, let’s try to dig deeper into images and layers.

A Docker image is built up from a series of layers. Each layer represents an instruction in the image’s Dockerfile. Each layer except the very last one is read-only.

Consider the following Dockerfile

FROM ubuntu:15.04
RUN apt-get -y install nginx
COPY index.html /var/www/html
CMD ["nginx","-g","daemon off;"]
  • This Dockerfile contains four commands each of which creates a layer
FROM: statements starts out by creating a layer from the ubuntu 15.04 image
RUN: Command install nginx
COPY: add some files to nginx www directory
CMD: command specify which command to run within the container 

Some points to keep in mind

  • Each layer is only a layer of differences from the layer before it.
  • The layers are stacked on top of each other
  • When you create a new container, you add a new writable layer on top of the underlying layers. This layer is often called the “container layer”
  • All changes made to the running container, such as writing new files, modifying existing files, and deleting files, are written to this thin writable container layer
  • If you want to check the layer of the image
$ docker image history nginx
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
f949e7d76d63        2 weeks ago         /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B                  
<missing>           2 weeks ago         /bin/sh -c #(nop)  STOPSIGNAL SIGTERM           0B                  
<missing>           2 weeks ago         /bin/sh -c #(nop)  EXPOSE 80                    0B                  
<missing>           2 weeks ago         /bin/sh -c ln -sf /dev/stdout /var/log/nginx…   0B                  
<missing>           2 weeks ago         /bin/sh -c set -x     && addgroup --system -…   56.8MB              
<missing>           2 weeks ago         /bin/sh -c #(nop)  ENV PKG_RELEASE=1~buster     0B                  
<missing>           2 weeks ago         /bin/sh -c #(nop)  ENV NJS_VERSION=0.3.5        0B                  
<missing>           2 weeks ago         /bin/sh -c #(nop)  ENV NGINX_VERSION=1.17.4     0B                  
<missing>           4 weeks ago         /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B                  
<missing>           4 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B                  
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:1901172d265456090…   69.2MB              

storage driver handles the details about the way these layers interact with each other.

Different storage drivers are available(eg: overlay2, devicemappers, aufs), which have advantages and disadvantages in different situations.

Difference between Image and Container

  • To sum up, what we have discussed so far, the major difference between a container and an image is the top writable layer.
  • All writes to the container that adds new or modifies existing data are stored in this writable layer.
  • When the container is deleted, the writable layer is also deleted.
  • The underlying image remains unchanged.
  • Because each container has its own writable container layer, and all changes are stored in this container layer, multiple containers can share access to the same underlying image and yet have their own data state(as shown in the above image)

Container size on Disk

  • To view the container size on disk
$ docker container ps -s
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES                   SIZE
424c8c51e49a        centos:centos7      "bash"                   4 hours ago         Exited (137) 3 hours ago                        objective_feynman       0B (virtual 202MB)
e2452dcdc6e1        centos:centos7      "bash"                   4 hours ago         Exited (137) 3 hours ago                        jovial_murdock          0B (virtual 202MB)
b4031e7df866        centos:centos7      "bash"                   4 hours ago         Exited (0) 4 hours ago                          charming_herschel       0B (virtual 202MB)
ce32f774053a        centos              "bash"                   5 hours ago         Exited (0) 4 hours ago                          quirky_cori             37B (virtual 220MB)
24756038f07a        busybox             "sh"                     5 hours ago         Exited (137) 3 hours ago                        mynonenetwork1          10B (virtual 1.22MB)
418657ff1fe4        ubuntu              "bash"                   5 hours ago         Exited (0) 3 hours ago                          mynonenetwork           60B (virtual 64.2MB)

* Containers, Images, Local Volume and Cache Size

$ docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              16                  8                   1.78GB              1.421GB (79%)
Containers          22                  0                   651MB               651MB (100%)
Local Volumes       0                   0                   0B                  0B
Build Cache         0                   0                   0B                  0B


OR if you are looking for more verbose output

$ docker system df -v
Images space usage:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE                SHARED SIZE         UNIQUE SIZE         CONTAINERS
multistage          latest              b536984e0b37        19 hours ago        7.586MB             5.577MB             2.009MB             1
single-stage        latest              3b5798ed94c5        19 hours ago        813.8MB             802.6MB             11.24MB             1
mycentosnewapache   latest              6d082f2b1c5e        22 hours ago        449.6MB             449.6MB             0B                  0
mycentosapache      latest              03f61d93b53e        22 hours ago        449.6MB             449.6MB             0B                  2
mycustomnginx1      latest              fe85db4f4704        44 hours ago        162.4MB             162.4MB             22B                 0
mycustomnginx       latest              35d8f0969f7c        44 hours ago        162.4MB             162.4MB             22B                 0
cmdimage            latest              9888f3568e1f        2 days ago          1.22MB              1.22MB              0B                  0
healthcheck_image   latest              ca8599a83d24        2 days ago          1.22MB              1.22MB              0B                  0
ping_docker         latest              6cb5dc1118a0        5 days ago          1.22MB              1.22MB              0B                  0
centos              latest              0f3e07c0138f        9 days ago          219.5MB             0B                  219.5MB             5
golang              1.13.1              52b59e9ead8e        2 weeks ago         802.6MB             802.6MB             0B                  0
nginx               latest              f949e7d76d63        2 weeks ago         126MB               0B                  126MB               2
ubuntu              bionic              2ca708c1c9cc        3 weeks ago         64.18MB             64.18MB             0B                  3
busybox             latest              19485c79a9bb        5 weeks ago         1.22MB              1.22MB              0B                  3
centos              centos7             67fa590cfc1c        7 weeks ago         201.9MB             201.9MB             0B                  4
alpine              3.10.2              961769676411        7 weeks ago         5.577MB             5.577MB             0B                  0

Containers space usage:

CONTAINER ID        IMAGE               COMMAND                  LOCAL VOLUMES       SIZE                CREATED             STATUS                      NAMES
424c8c51e49a        centos:centos7      "bash"                   0                   0B                  5 hours ago         Exited (137) 3 hours ago    objective_feynman
e2452dcdc6e1        centos:centos7      "bash"                   0                   0B                  5 hours ago         Exited (137) 3 hours ago    jovial_murdock
b4031e7df866        centos:centos7      "bash"                   0                   0B                  5 hours ago         Exited (0) 5 hours ago      charming_herschel
ce32f774053a        centos              "bash"                   0                   37B                 5 hours ago         Exited (0) 5 hours ago      quirky_cori
24756038f07a        busybox             "sh"                     0                   10B                 5 hours ago         Exited (137) 3 hours ago    mynonenetwork1
418657ff1fe4        ubuntu              "bash"                   0                   60B                 5 hours ago         Exited (0) 3 hours ago      mynonenetwork
efb9e8f5615a        ubuntu              "bash"                   0                   88.2MB              5 hours ago         Exited (0) 3 hours ago      mynewhost1
88d9f7bfa898        ubuntu              "bash"                   0                   88.1MB              6 hours ago         Exited (0) 3 hours ago      mynewhost
94059b00c326        centos              "bash"                   0                   44.7MB              6 hours ago         Exited (0) 3 hours ago      myhost
e0fa509a4a38        centos              "/bin/bash"              0                   53B                 6 hours ago         Exited (0) 3 hours ago      mycustombridge02
757f1a00d053        centos              "/bin/bash"              0                   0B                  6 hours ago         Exited (0) 3 hours ago      mycustombridge01
394b8e5255ed        busybox             "sh"                     0                   0B                  7 hours ago         Exited (137) 3 hours ago    bridge02
46ea63e383c8        busybox             "sh"                     0                   77B                 7 hours ago         Exited (137) 3 hours ago    bridge01
dac9b0fca295        multistage          "./helloworld"           0                   0B                  19 hours ago        Exited (0) 19 hours ago     laughing_edison
3fd2abfc0e0f        single-stage        "./helloworld"           0                   0B                  19 hours ago        Exited (0) 19 hours ago     beautiful_torvalds
993f69137ed7        mycentosapache      "/usr/sbin/apachectl…"   0                   1.18kB              22 hours ago        Exited (137) 13 hours ago   mycentoscustomapache1
c04a61a61e4f        mycentosapache      "/usr/sbin/apachectl…"   0                   5.1kB               22 hours ago        Exited (137) 13 hours ago   mycentoscustomapache
c9c79d7b72cd        8f2ae044a233        "/bin/sh -c 'yum -y …"   0                   215MB               22 hours ago        Exited (1) 22 hours ago     mystifying_darwin
35355550ab59        centos:centos7      "/bin/bash"              0                   215MB               22 hours ago        Exited (137) 13 hours ago   mycentostest
733c8448e2c8        centos              "bash"                   0                   43B                 25 hours ago        Exited (0) 13 hours ago     mycentos
789f45a23f9f        nginx               "nginx -g 'daemon of…"   0                   15B                 29 hours ago        Exited (0) 13 hours ago     mynginxnew
b4db78c85559        nginx               "nginx -g 'daemon of…"   0                   0B                  29 hours ago        Exited (0) 29 hours ago     mynginx

Local Volumes space usage:

VOLUME NAME         LINKS               SIZE

Build cache usage: 0B

CACHE ID            CACHE TYPE          SIZE                CREATED             LAST USED           USAGE               SHARED

The Copy-on-Write Mechanism

When we launch an image, the Docker engine does not make a full copy of the already stored image. Instead, it uses something called the copy-on-write mechanism. This is a standard UNIX pattern that provides a single shared copy of some data until the data is modified.

To do this, changes between the image and the running container are tracked. Just before any write operation is performed in the running container, a copy of the file that would be modified is placed on the writeable layer of the container, and that is where the write operation takes place. Hence the name, “copy-on-write”.

If this wasn’t happening, each time you launched an image, a full copy of the filesystem would have to be made. This would add time to the startup process and would end up using a lot of disk space.

Let’s take a simple example, to understand how Image Layer works,below is my dockerfile

FROM busybox
RUN dd if=/dev/zero of=/tmp/test1 bs=1M count=50
RUN dd if=/dev/zero of=/tmp/test2 bs=1M count=50
  • Try to build an image out of this Dockerfile
$ docker build -t mylayer1 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM busybox
 ---> 19485c79a9bb
Step 2/3 : RUN dd if=/dev/zero of=/tmp/test1 bs=1M count=50
 ---> Running in 2495248f03df
50+0 records in
50+0 records out
52428800 bytes (50.0MB) copied, 0.030272 seconds, 1.6GB/s
Removing intermediate container 2495248f03df
 ---> 4c557085e440
Step 3/3 : RUN dd if=/dev/zero of=/tmp/test2 bs=1M count=50
 ---> Running in aca8401f91cc
50+0 records in
50+0 records out
52428800 bytes (50.0MB) copied, 0.029855 seconds, 1.6GB/s
Removing intermediate container aca8401f91cc
 ---> bfbfff240e48
Successfully built bfbfff240e48
Successfully tagged mylayer1:latest
  • Check the image size
$  docker build -t mylayer1 .
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mylayer1              latest              bfbfff240e48        4 seconds ago       106MB
  • Let’s make little modification to the Dockefile by trying to remove these two files
FROM busybox
RUN dd if=/dev/zero of=/tmp/test1 bs=1M count=50
RUN dd if=/dev/zero of=/tmp/test2 bs=1M count=50
RUN rm -rf /tmp/test1
RUN rm -rf /tmp/test2
  • Try to build the image again
$ docker build -t mylayer2 .
Sending build context to Docker daemon  2.048kB
Step 1/5 : FROM busybox
 ---> 19485c79a9bb
Step 2/5 : RUN dd if=/dev/zero of=/tmp/test1 bs=1M count=50
 ---> Using cache
 ---> 4c557085e440
Step 3/5 : RUN dd if=/dev/zero of=/tmp/test2 bs=1M count=50
 ---> Using cache
 ---> bfbfff240e48
Step 4/5 : RUN rm -rf /tmp/test1
 ---> Running in 9059f97edae0
Removing intermediate container 9059f97edae0
 ---> 3987aea24cc7
Step 5/5 : RUN rm -rf /tmp/test2
 ---> Running in 9dde88d940ee
Removing intermediate container 9dde88d940ee
 ---> fd617a5810fa
Successfully built fd617a5810fa
Successfully tagged mylayer2:latest
  • Check the image size
$ docker image ls
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mylayer2              latest              fd617a5810fa        4 seconds ago       106MB
  • It’s the same, this doesn’t look, right? As we introduce two more new layer
RUN rm -rf /tmp/test1
RUN rm -rf /tmp/test2
  • As I mentioned above, image is just the collection of layer, and we are deleting test1 and test2 at different layer and that is the reason the size of the image is the same
  • Let’s move those remove instruction in the same layer
FROM busybox
RUN dd if=/dev/zero of=/tmp/test1 bs=1M count=50 && rm -rf /tmp/test1
RUN dd if=/dev/zero of=/tmp/test2 bs=1M count=50 && rm -rf /tmp/test2
  • Re-build the image
$ docker build -t mylayer3 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM busybox
 ---> 19485c79a9bb
Step 2/3 : RUN dd if=/dev/zero of=/tmp/test1 bs=1M count=50 && rm -rf /tmp/test1
 ---> Running in 58ecae5b4282
50+0 records in
50+0 records out
52428800 bytes (50.0MB) copied, 0.034398 seconds, 1.4GB/s
Removing intermediate container 58ecae5b4282
 ---> 062fa630bd3d
Step 3/3 : RUN dd if=/dev/zero of=/tmp/test2 bs=1M count=50 && rm -rf /tmp/test2
 ---> Running in 493d7e59ea5b
50+0 records in
50+0 records out
52428800 bytes (50.0MB) copied, 0.029036 seconds, 1.7GB/s
Removing intermediate container 493d7e59ea5b
 ---> 13f26e00997f
Successfully built 13f26e00997f
Successfully tagged mylayer3:latest
  • Check the size
$ docker image ls
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mylayer3              latest              13f26e00997f        7 seconds ago       1.22MB

Looks promising 🙂

What is Dangling Images?

Dangling images are layers that have no relationship to any tagged images. They no longer serve a purpose and consume disk space

Let take a scenario in which these none layers will be created?

  • Let pull apache image from the docker registry
$ docker image pull httpd
Using default tag: latest
latest: Pulling from library/httpd
b8f262c62ec6: Already exists 
2c31b9311798: Pull complete 
7422a3cdf4e3: Pull complete 
1919d4fbf9e1: Pull complete 
60812fa1ab4c: Pull complete 
Digest: sha256:39d7d9a3ab93c0ad68ee7ea237722ed1b0016ff6974d80581022a53ec1e58797
Status: Downloaded newer image for httpd:latest
docker.io/library/httpd:latest
  • Start the container
$ docker container run -d httpd
1b97fb9667c0cc373c426e1a8ac3df6703c9dc91d63b46e1a52f8cf3a499b935
  • Check if it’s running
$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
1b97fb9667c0        httpd               "httpd-foreground"       6 seconds ago       Up 4 seconds        80/tcp                 tender_kirch
  • Now let’s try to remove the image, associated with this container
$ docker image rm httpd
Error response from daemon: conflict: unable to remove repository reference "httpd" (must force) - container 1b97fb9667c0 is using its referenced image 19459a872194

As the container is using this image, we can’t delete it, so the only way we can delete this image by using -f flag

$ docker image rm -f httpd
Untagged: httpd:latest
Untagged: httpd@sha256:39d7d9a3ab93c0ad68ee7ea237722ed1b0016ff6974d80581022a53ec1e58797
  • Pay special attention to the output of the above command, it says untagged the image
  • Now if you try to list the image, used by the container, you will see the output like this and it creates the dangling image.
$ docker image ls |grep 19459a872194
<none>              <none>              19459a872194        3 weeks ago         154MB
  • Now the question is how to clean these kinds of images
  • First, stop and remove the container
$ docker container rm -f 1b97fb9667c0
1b97fb9667c0
  • Run docker image prune to clean dangling image
$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:19459a87219415cc5751fb48e9aa37ea580b98eed1fe784e76c4bc3c9b3b0741
deleted: sha256:ebefc32f49862ee9f4f672b338f29aefe056971bcc88912ed87968f5f75b0ae7
deleted: sha256:fe3c18ca62ea3f45b69ad643869c6fd3ab567a5ad8207adf8d071d54c9152597
deleted: sha256:62d97f753807d11f7ab72f8ae1911ff22617c9134e3edec13bf909493edb025c
deleted: sha256:c4e27a02760c0a8711ac5e9f7e0fc7cffeb38b44b4041d0c6ded5087586727a8

Total reclaimed space: 84.62MB
  • Verify it
$ docker image ls |grep 19459a872194

NOTE: If you build an image without specifying -t (i.e docker build . ), the image will appear in the list of dangling images as it has no association with the tagged images.

How to create an image out of a container

  • Let’s take a scenario, where I created a new container out of busybox image
$ docker container run -dt --name mytestcont busybox
8945e3832ac3abee79250b8bb5288f62296f8430cce954f9a6db331e31e232c8
  • Logged into that container
$ docker container exec -it 8945e3832ac3 sh
/ #

# Create a new file
/ # touch abc.txt
/ #
  • To create a docker image out of this container, I need to use docker container commit command
  • The command will look like this
docker container commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
$ docker container commit -a "plakhera" -m "my docker busybox image from container" 8945e3832ac3 mytestbusyboximage
sha256:aca40c0caa3b8159d2350db6240f368f6e75b06bdc1c98af268858a8089a202c

  -a, --author string    Author (e.g., "John Hannibal Smith <[email protected]>")
  -m, --message string   Commit message	
  • To verify it, run the new container using this image
$ docker container run -it mytestbusyboximage sh
/ # ls -l
total 20
-rw-r--r--    1 root     root             0 Oct 11 05:46 abc.txt

NOTE: By default, the container being committed and its process will be paused while the image is committed.

This is a good place, to stop for Day 8

Please follow me with my Journey

This time to make learning more interactive, I am adding

  • Slack
  • Meetup

Please feel free to join this group.

Slack: 

https://100daysofdevops.slack.com/join/shared_invite/enQtNzg1MjUzMzQzMzgxLWM4Yjk0ZWJiMjY4ZWE3ODBjZjgyYTllZmUxNzFkNTgxZjQ4NDlmZjkzODAwNDczOTYwOTM2MzlhZDNkM2FkMDA

Meetup Group

If you are in the bay area, please join this meetup group https://www.meetup.com/100daysofdevops/

2 Replies to “21 Days of Docker-Day 8-Docker Images, Layers & Containers”

Comments are closed.