Building A Docker Image Docker

Aug 9th, 2019 - written by Kimserey with .

Last week we looked into the definition of a Dockerfile. We saw that a Dockerfile describes how an image will be built, the next obvious step is to dig into the building process of the image by looking at the logs and exploring the image layers.

Building An Image

Taking an example of a dotnet Dockerfile,

1
2
3
4
5
6
7
8
9
10
FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
WORKDIR /src
COPY . .
WORKDIR /src/WebApplication1
RUN dotnet publish WebApplication1.csproj -c Release -o /app

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim 
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "WebApplication1.dll"]

we can run a docker build which would build the image:

1
docker build -f .\WebApplication1\Dockerfile -t webapplication1 ./

-f specifies the location of the Dockerfile, while -t specifies a tag for the image. We actually did not give any tag in this case which made the image have the default tag of latest. The format to provide a tag is {image_name}:{tag}. The last argument ./ is the build context which is the folder from where the image will be built, where we will be able to copy files from.

If you want to know more about how to build a Dockerfile, you can refer to my previous blog post.

Once we run docker build we get the following logs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Sending build context to Docker daemon  18.94kB
Step 1/9 : FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
2.2-stretch: Pulling from dotnet/core/sdk
Digest: sha256:808da273022a5bb7b2c15fb80acb85e0432fa79993bbf1e6e982d4e01349c191
Status: Downloaded newer image for mcr.microsoft.com/dotnet/core/sdk:2.2-stretch
 ---> 3af77ac73731
Step 2/9 : WORKDIR /src
 ---> Running in 0f70be63faae
Removing intermediate container 0f70be63faae
 ---> 3194c452d135
Step 3/9 : COPY . .
 ---> b656b9271c5c
Step 4/9 : WORKDIR /src/WebApplication1
 ---> Running in 6648557dbbbb
Removing intermediate container 6648557dbbbb
 ---> e3fcdcb1792e
Step 5/9 : RUN dotnet publish WebApplication1.csproj -c Release -o /app
 ---> Running in 7fbc5519b556
Microsoft (R) Build Engine version 16.2.32702+c4012a063 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 21.52 sec for /src/WebApplication1/WebApplication1.csproj.
  WebApplication1 -> /src/WebApplication1/bin/Release/netcoreapp2.2/WebApplication1.dll
  WebApplication1 -> /app/
Removing intermediate container 7fbc5519b556
 ---> c6bc468635fe
Step 6/9 : FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim
 ---> 3ee0429b27ad
Step 7/9 : WORKDIR /app
 ---> Running in 921f7feed14a
Removing intermediate container 921f7feed14a
 ---> 01e4a68b3a49
Step 8/9 : COPY --from=build /app .
 ---> 9857d4ca4813
Step 9/9 : ENTRYPOINT ["dotnet", "WebApplication1.dll"]
 ---> Running in a9345c006948
Removing intermediate container a9345c006948
 ---> d9dc22edcb71
Successfully built d9dc22edcb71
Successfully tagged webapplication1:latest

The first line indicates to us the size of the build context,

1
Sending build context to Docker daemon  18.94kB

even though our whole application folder isn’t that size, we stripped off all unnecessary files by using a .dockerignore file, reducing the build context to 18.94kb.

Then we can see the first log for the execution of the first instruction from the dockerfile:

1
2
3
4
5
Step 1/9 : FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build
2.2-stretch: Pulling from dotnet/core/sdk
Digest: sha256:808da273022a5bb7b2c15fb80acb85e0432fa79993bbf1e6e982d4e01349c191
Status: Downloaded newer image for mcr.microsoft.com/dotnet/core/sdk:2.2-stretch
 ---> 3af77ac73731

1/9 is the step number from the Dockerfile, our first instruction was to build our image starting from the dotnet/core/sdk image. The image id 3af77ac73731 corresponds to the dotnet/core/sdk image sha that we are using. For example, here if we were to pull locally the image mcr.microsoft.com/dotnet/core/sdk:2.2-stretch, we will see that its id is 3af77ac73731.

The next log step,

1
2
3
4
Step 2/9 : WORKDIR /src
 ---> Running in 0f70be63faae
Removing intermediate container 0f70be63faae
 ---> 3194c452d135

explains that the work directory is set to /src within the image. It runs in an intermediate container 0f70be63faae which creates a /src folder in the container resulting in a layer 3194c452d135.

Then we have a copy,

1
2
Step 3/9 : COPY . .
 ---> b656b9271c5c

Copying files from the build context to the current work directory /src, resulting in a layer b656b9271c5c.

Next we have another set of work directory,

1
2
3
4
Step 4/9 : WORKDIR /src/WebApplication1
 ---> Running in 6648557dbbbb
Removing intermediate container 6648557dbbbb
 ---> e3fcdcb1792e

resulting in a layer e3fcdcb1792e. Since we previously copied the files, we are then able to publish the application.

1
2
3
4
5
6
7
8
9
10
Step 5/9 : RUN dotnet publish WebApplication1.csproj -c Release -o /app
 ---> Running in 7fbc5519b556
Microsoft (R) Build Engine version 16.2.32702+c4012a063 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 21.52 sec for /src/WebApplication1/WebApplication1.csproj.
  WebApplication1 -> /src/WebApplication1/bin/Release/netcoreapp2.2/WebApplication1.dll
  WebApplication1 -> /app/
Removing intermediate container 7fbc5519b556
 ---> c6bc468635fe

The command is ran in a intermediate container 7fbc5519b556 and results in a layer c6bc468635fe. In the initial FROM, we have name the stage AS build. The five layers constitute the image build which is an image containing the published application under the /app/ folder.

Then similarly the rest of the steps are executed in containers and result in layers being stacked until the last layer constituting the webapplication1 image.

1
2
3
4
5
6
7
8
9
10
11
12
Step 6/9 : FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim
 ---> 3ee0429b27ad
Step 7/9 : WORKDIR /app
 ---> Running in 921f7feed14a
Removing intermediate container 921f7feed14a
 ---> 01e4a68b3a49
Step 8/9 : COPY --from=build /app .
 ---> 9857d4ca4813
Step 9/9 : ENTRYPOINT ["dotnet", "WebApplication1.dll"]
 ---> Running in a9345c006948
Removing intermediate container a9345c006948
 ---> d9dc22edcb71

The last layer id d9dc22edcb71 is the image id resulting of the docker build command.

1
2
Successfully built d9dc22edcb71
Successfully tagged webapplication1:latest

Once the image is built, we can find it from docker image ls:

1
2
3
4
5
6
7
$ docker image ls
REPOSITORY                                        TAG                      IMAGE ID            CREATED             SIZE
<none>                                            <none>                   c6bc468635fe        39 seconds ago      1.79GB
webapplication1                                   latest                   d9dc22edcb71        2 hours ago         262MB
mcr.microsoft.com/dotnet/core/sdk                 2.2-stretch              3af77ac73731        9 days ago          1.74GB
mcr.microsoft.com/dotnet/core/aspnet              2.2-stretch-slim         3ee0429b27ad        9 days ago          260MB
mcr.microsoft.com/dotnet/core/aspnet              latest                   3ee0429b27ad        9 days ago          260MB

And we can look at its configuration using docker inspect d9dc22edcb71:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
$ docker inspect d9dc22edcb71
[
    {
        "Id": "sha256:d9dc22edcb716a92f5e68f915fc5b3debced288737066af5ef7821a0ba3375d4",
        "RepoTags": [
            "webapplication1:latest"
        ],
        "RepoDigests": [],
        "Parent": "sha256:9857d4ca4813255a56469450829fe2a9f85fa2b93c8622061fea0ef93083fe24",
        "Comment": "",
        "Created": "2019-08-02T18:03:37.1689906Z",
        "Container": "a9345c006948bd6eb4f106571c2e9f114a57deb088fcf0a58a752c3331a829e1",
        "ContainerConfig": {
            "Hostname": "a9345c006948",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "ASPNETCORE_URLS=http://+:80",
                "DOTNET_RUNNING_IN_CONTAINER=true",
                "ASPNETCORE_VERSION=2.2.6"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "ENTRYPOINT [\"dotnet\" \"WebApplication1.dll\"]"
            ],
            "Image": "sha256:9857d4ca4813255a56469450829fe2a9f85fa2b93c8622061fea0ef93083fe24",
            "Volumes": null,
            "WorkingDir": "/app",
            "Entrypoint": [
                "dotnet",
                "WebApplication1.dll"
            ],
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "19.03.1",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "ASPNETCORE_URLS=http://+:80",
                "DOTNET_RUNNING_IN_CONTAINER=true",
                "ASPNETCORE_VERSION=2.2.6"
            ],
            "Cmd": null,
            "Image": "sha256:9857d4ca4813255a56469450829fe2a9f85fa2b93c8622061fea0ef93083fe24",
            "Volumes": null,
            "WorkingDir": "/app",
            "Entrypoint": [
                "dotnet",
                "WebApplication1.dll"
            ],
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 261541404,
        "VirtualSize": 261541404,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/15c7304f08de55890d6de74d9a5c0fbdc9cea87019ac01d4c3d763f157893169/diff:/var/lib/docker/overlay2/57d5f1900c0e4859a05b1911af056a1e491c8f029a643d51ac51fcc94854b9ba/diff:/var/lib/docker/overlay2/a0decbde6391daf0bc065778ddec12e27fa4de57d3d92c9658f842e795452921/diff:/var/lib/docker/overlay2/ef9157128986fbeb6625b5bbe2cc4ef6211105527b1f1f9b5478ab2e57a37dbf/diff:/var/lib/docker/overlay2/7213c0db8cf3f2bb71f8709b51d8debf24bce93104a5f375e2b710b369bace4c/diff",
                "MergedDir": "/var/lib/docker/overlay2/06fd28ea362b73edb62ee0aefd31d4d4a1015c956ed0916e7e9bf65669f0b025/merged",
                "UpperDir": "/var/lib/docker/overlay2/06fd28ea362b73edb62ee0aefd31d4d4a1015c956ed0916e7e9bf65669f0b025/diff",
                "WorkDir": "/var/lib/docker/overlay2/06fd28ea362b73edb62ee0aefd31d4d4a1015c956ed0916e7e9bf65669f0b025/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:d56055da3352f918f4d8a42350385ea5b10d0906e746a8fbb4b850f9284deee5",
                "sha256:9c894eb0930bc544405419e57fdc34dec3d22ce42e165829fe9e03c6dd8e0758",
                "sha256:c6c9de941dfa6e4849e7f0026e180a2d4e5d9263316e9ab6676480c7e299e9e4",
                "sha256:6339615de93e6000914871a96e377bd9c4828f23e02491d5fa30ccebd2c97cdb",
                "sha256:09874d0e88c646a7e7ac2d94076cfaa50c7e63555ce7e4ff6ff4b6596c6ce099",
                "sha256:87f4ccb229bb7484f733ebab6648e7493a2c550167f251819f72523124eacec6"
            ]
        },
        "Metadata": {
            "LastTagTime": "2019-08-02T18:16:13.109039Z"
        }
    }
]

The containerConfig property is particularly interesting:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
    "Env": [
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "ASPNETCORE_URLS=http://+:80",
        "DOTNET_RUNNING_IN_CONTAINER=true",
        "ASPNETCORE_VERSION=2.2.6"
    ],
    "Cmd": [
        "/bin/sh",
        "-c",
        "#(nop) ",
        "ENTRYPOINT [\"dotnet\" \"WebApplication1.dll\"]"
    ],
    "WorkingDir": "/app"
}

It showcases the environment variables set for our image, the command which will be executed when running the container and the working directory where the command is executed from.

Now you might have noticed the <none> c6bc468635fe image of size 1.79GB which showed up in the docker image ls. The CLI shows <none> for untagged images. If we look back at the logs, we can see that c6bc468635fe corresponds to our last layer of our build stage:

1
2
3
4
5
6
7
8
9
10
Step 5/9 : RUN dotnet publish WebApplication1.csproj -c Release -o /app
 ---> Running in 7fbc5519b556
Microsoft (R) Build Engine version 16.2.32702+c4012a063 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 21.52 sec for /src/WebApplication1/WebApplication1.csproj.
  WebApplication1 -> /src/WebApplication1/bin/Release/netcoreapp2.2/WebApplication1.dll
  WebApplication1 -> /app/
Removing intermediate container 7fbc5519b556
 ---> c6bc468635fe

The role of the stage was to provide the tools to publish the application and allow the next stage to just copy the files already precompiled hence allowing the resulting image to only contain the runtime and strip off the development SDK.

We can see the difference in size from the SDK image of 1.74GB compared to the runtime image of 260MB. The difference is non negligible therefore we have to make sure to start from a runtime image and leave behind the SDK image.

One advantage of keeping around the intermediate image is for caching purposes. If we rerun the build, we will see that the step is taken from cache:

1
2
3
Step 5/9 : RUN dotnet publish WebApplication1.csproj -c Release -o /app
 ---> Using cache
 ---> c6bc468635fe

If we prune the images docker image prune, the <none> image will be cleared and will result in recomputing the layer on build.

The cached layers can be seen using docker image ls -a. For example if we rebuild the image:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ docker build -f WebApplication1\Dockerfile -t webapplication1 ./                      
Sending build context to Docker daemon  18.94kB                                         
Step 1/9 : FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build                  
 ---> 3af77ac73731                                                                      
Step 2/9 : WORKDIR /src                                                                 
 ---> Running in 8d3e636315d3                                                           
Removing intermediate container 8d3e636315d3                                            
 ---> 9bcd7944b833                                                                      
Step 3/9 : COPY . .                                                                     
 ---> 156063c13984                                                                      
Step 4/9 : WORKDIR /src/WebApplication1                                                 
 ---> Running in 2a1d5a2063ac                                                           
Removing intermediate container 2a1d5a2063ac                                            
 ---> 5608d2c9e274                                                                      
Step 5/9 : RUN dotnet publish WebApplication1.csproj -c Release -o /app                 
 ---> Running in edbd56b760ba                                                           
Microsoft (R) Build Engine version 16.2.32702+c4012a063 for .NET Core                   
Copyright (C) Microsoft Corporation. All rights reserved.                               
                                                                                        
  Restore completed in 20.93 sec for /src/WebApplication1/WebApplication1.csproj.       
  WebApplication1 -> /src/WebApplication1/bin/Release/netcoreapp2.2/WebApplication1.dll 
  WebApplication1 -> /app/                                                              
Removing intermediate container edbd56b760ba                                            
 ---> 766e5f5eda98                                                                      
Step 6/9 : FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim                   
 ---> 3ee0429b27ad                                                                      
Step 7/9 : WORKDIR /app                                                                 
 ---> Using cache                                                                       
 ---> 01e4a68b3a49                                                                      
Step 8/9 : COPY --from=build /app .                                                     
 ---> Using cache                                                                       
 ---> 9857d4ca4813                                                                      
Step 9/9 : ENTRYPOINT ["dotnet", "WebApplication1.dll"]                                 
 ---> Using cache                                                                       
 ---> d9dc22edcb71                                                                      
Successfully built d9dc22edcb71                                                         
Successfully tagged webapplication1:latest

We can see the untagged image and our webapplication1 image:

1
2
3
4
$ docker image ls
REPOSITORY                                        TAG                      IMAGE ID            CREATED              SIZE
<none>                                            <none>                   766e5f5eda98        About a minute ago   1.79GB
webapplication1                                   latest                   d9dc22edcb71        2 hours ago          262M

And when we display all layers:

1
2
3
4
5
6
7
8
9
$ docker image ls -a
REPOSITORY                                        TAG                      IMAGE ID            CREATED              SIZE
<none>                                            <none>                   766e5f5eda98        50 seconds ago       1.79GB
<none>                                            <none>                   5608d2c9e274        About a minute ago   1.74GB
<none>                                            <none>                   156063c13984        About a minute ago   1.74GB
<none>                                            <none>                   9bcd7944b833        About a minute ago   1.74GB
webapplication1                                   latest                   d9dc22edcb71        2 hours ago          262MB
<none>                                            <none>                   01e4a68b3a49        2 hours ago          260MB
<none>                                            <none>                   9857d4ca4813        2 hours ago          262MB

We see that 5608d2c9e274, 156063c13984 and 9bcd7944b833 are layers for 766e5f5eda98. While 01e4a68b3a49 and 9857d4ca4813 are layers for webapplication1 d9dc22edcb71.

If we delete the image webapplication1, all the layers for webapplication1 will be removed but the intermediate stage 766e5f5eda98 will not be removed. Therefore if we build again, Docker will be able to take advantage of the caching layer.

Caching is an important part of Docker build as it allows the build to be quicker.

On Windows, with Powershell we can use Measure-Command { <your command here> | Write-Host } to measure the time of the build.

Exploring Layers

Building an image consists mostly of copying files. Copying from the build context, or copying from different stages. It can be very easy to mistakenly copy the wrong folder or the wrong files and have the image build with the wrong entrypoint.

To explore docker image we can use a tool called Dive, which allows us to explore a Docker image, or layer content. Doing so allows us to check for mistake and also allows us to look for potential optimization in regards to the size.

Dive can be downloaded from its Github repository.

After downloading it, we simple run dive <image> and we will enter the GUI allowing us to inspect the content of our image.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[● Layers]──────────────────────────────────────────────────────── [Current Layer Contents]─────────────────────────────────────────
Cmp   Size  Command                                                Permission     UID:GID       Size  Filetree
     55 MB  FROM sha256:d56055da                                   drwxr-xr-x         0:0     4.6 MB  ├── bin
     44 MB  apt-get update     && apt-get install -y --no-install- -rwxr-xr-x         0:0     1.1 MB  │   ├── bash
    7.0 MB  apt-get update     && apt-get install -y --no-install- -rwxr-xr-x         0:0      36 kB  │   ├── cat
    154 MB  curl -SL --output aspnetcore.tar.gz https://dotnetcli. -rwxr-xr-x         0:0      64 kB  │   ├── chgrp
       0 B  #(nop) WORKDIR /app                                    -rwxr-xr-x         0:0      60 kB  │   ├── chmod
    1.1 MB  #(nop) COPY dir:5df981166cc4bf1e0b1d1fbb31d69f366ee88f -rwxr-xr-x         0:0      64 kB  │   ├── chown
                                                                   -rwxr-xr-x         0:0     130 kB  │   ├── cp
[Layer Details]─────────────────────────────────────────────────── -rwxr-xr-x         0:0     117 kB  │   ├── dash
                                                                   -rwxr-xr-x         0:0     105 kB  │   ├── date
Digest: sha256:d56055da3352f918f4d8a42350385ea5b10d0906e746a8fbb4b -rwxr-xr-x         0:0      77 kB  │   ├── dd
850f9284deee5                                                      -rwxr-xr-x         0:0      86 kB  │   ├── df
Command:                                                           -rwxr-xr-x         0:0     131 kB  │   ├── dir
#(nop) ADD file:966bd7368f1e5a3e40fe5fab63ebe3c04719f677cc49385462 -rwxr-xr-x         0:0      73 kB  │   ├── dmesg
e4dfda4c4096fb in /                                                -rwxrwxrwx         0:0        0 B  │   ├── dnsdomainname → hostname
                                                                   -rwxrwxrwx         0:0        0 B  │   ├── domainname → hostname 
[Image Details]─────────────────────────────────────────────────── -rwxr-xr-x         0:0      32 kB  │   ├── echo
                                                                   -rwxr-xr-x         0:0       28 B  │   ├── egrep
Total Image size: 262 MB                                           -rwxr-xr-x         0:0      32 kB  │   ├── false
Potential wasted space: 3.7 MB                                     -rwxr-xr-x         0:0       28 B  │   ├── fgrep
Image efficiency score: 99 %                                       -rwxr-xr-x         0:0      62 kB  │   ├── findmnt
                                                                   -rwxr-xr-x         0:0     215 kB  │   ├── grep
Count   Total Space  Path                                          -rwxr-xr-x         0:0     2.3 kB  │   ├── gunzip
    2        1.5 MB  /var/cache/debconf/templates.dat              -rwxr-xr-x         0:0     5.9 kB  │   ├── gzexe
    2        1.5 MB  /var/cache/debconf/templates.dat-old          -rwxr-xr-x         0:0     102 kB  │   ├── gzip
    3        244 kB  /var/lib/dpkg/status                          -rwxr-xr-x         0:0      19 kB  │   ├── hostname
    3        244 kB  /var/lib/dpkg/status-old                      -rwxr-xr-x         0:0      56 kB  │   ├── ln
    2         29 kB  /var/log/dpkg.log                             -rwxr-xr-x         0:0      53 kB  │   ├── login
    3         26 kB  /etc/ld.so.cache                              -rwxr-xr-x         0:0     131 kB  │   ├── ls
    2         23 kB  /var/cache/debconf/config.dat                 -rwxr-xr-x         0:0      81 kB  │   ├── lsblk
    2         18 kB  /var/log/apt/term.log                         -rwxr-xr-x         0:0      81 kB  │   ├── mkdir
    3         15 kB  /var/lib/apt/extended_states                  -rwxr-xr-x         0:0      69 kB  │   ├── mknod
    2         14 kB  /var/cache/ldconfig/aux-cache                 -rwxr-xr-x         0:0      44 kB  │   ├── mktemp
    2         11 kB  /var/log/apt/eipp.log.xz                      -rwxr-xr-x         0:0      40 kB  │   ├── more
    2        9.1 kB  /var/cache/debconf/config.dat-old             -rwxr-xr-x         0:0      44 kB  │   ├── mount
    2        2.6 kB  /var/log/apt/history.log                      -rwxr-xr-x         0:0      15 kB  │   ├── mountpoint
    2           0 B  /var/lib/dpkg/triggers/Unincorp               -rwxr-xr-x         0:0     126 kB  │   ├── mv
    3           0 B  /var/lib/dpkg/triggers/Lock                   -rwxrwxrwx         0:0        0 B  │   ├── nisdomainname → hostname
    3           0 B  /var/lib/dpkg/lock                            -rwxrwxrwx         0:0        0 B  │   ├── pidof → /sbin/killall5
                                                                   -rwxr-xr-x         0:0      36 kB  │   ├── pwd
                                                                   -rwxrwxrwx         0:0        0 B  │   ├── rbash → bash

On the top left we can see the different instructions which composed the image, while on the right we can explore the filesystem in the resulting image. Layers can also be explored using docker history <image>.

1
2
3
4
5
6
7
8
9
10
11
12
$ docker history webapplication1
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
1805da23bee2        11 hours ago        /bin/sh -c #(nop)  ENTRYPOINT ["dotnet" "Web…   0B
becdc8ed4269        11 hours ago        /bin/sh -c #(nop) COPY dir:5df981166cc4bf1e0…   1.09MB
a027a3092b27        11 hours ago        /bin/sh -c #(nop) WORKDIR /app                  0B
3ee0429b27ad        9 days ago          /bin/sh -c curl -SL --output aspnetcore.tar.…   154MB
<missing>           9 days ago          /bin/sh -c #(nop)  ENV ASPNETCORE_VERSION=2.…   0B
<missing>           9 days ago          /bin/sh -c apt-get update     && apt-get ins…   7.02MB
<missing>           9 days ago          /bin/sh -c #(nop)  ENV ASPNETCORE_URLS=http:…   0B
<missing>           9 days ago          /bin/sh -c apt-get update     && apt-get ins…   43.8MB
<missing>           3 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>           3 weeks ago         /bin/sh -c #(nop) ADD file:966bd7368f1e5a3e4…   55.3MB

Multiple Tags

Lastly tagging multiple time an image as part of building is one way of keeping track of different build for a particular image. For example we could keep track of the version number:

1
docker build -f WebApplication1\Dockerfile -t webapplication1 -t webapplication1:1.0.0 ./

Then we would have the same image with two tags allowing us to easily identify which version is the latest version:

1
2
3
4
$ docker image ls
REPOSITORY                                        TAG                      IMAGE ID            CREATED             SIZE
webapplication1                                   1.0.0                    1805da23bee2        27 minutes ago      262MB
webapplication1                                   latest                   1805da23bee2        27 minutes ago      262MB

We can also see examples on Docker hub of how tags are used to build the same image on different distros.

Conclusion

Today we looked into the details of a Docker image. How it was built, what were the logs provided and what they meant. We looked into the details of what the layers were when building the image, and we looked at how the layers were cache and where they could be found. We then looked into a tool to explore the filesystem built by the layers and we completed the post by looking into the purpose of multiple tags. I hope you liked this post and I see you on the next one!

External Sources

Designed, built and maintained by Kimserey Lam.