Layered Architecture And Dockerfiles

What is a docker file?

A docker file is a document that contains scripts or instructions using which an image can be created.

Creating a docker file -

learning-ocean:~ gaurav$ vi Dockerfile

The docker build command by default builds an image by executing the instructions present in the file named as Dockerfile in the current working directory. So it is a best practice to always name your docker file as Dockerfile, if you name the file as something else then you need to specify the docker file name explicitly by using -f option along with the docker build command.

FROM ubuntu:16.04

A docker file with the above content will build an image from the base docker image ubuntu:16.04.

FROM keyword is used to specify the base image and will create a layer using docker image ubuntu:16.04 as a base image.

Note: Best practices related to dockerfile can be found on docker docs.

learning-ocean:~ gaurav$ docker image build -t myubuntu:1 .

The above command will build an image from the dockerfile we created above with the image name as myubuntu and tag name 1. Dot(.) at the end specified the current working directory.

gaurav@learning-ocean:~/dockerfiles$ docker image build -t myubuntu:1 .
Sending build context to Docker daemon  2.048kB
Step 1/1 : FROM ubuntu:16.04
 ---> 065cf14a189c
Successfully built 065cf14a189c
Successfully tagged myubuntu:1
gaurav@learning-ocean:~/dockerfiles$

On executing the build command, an image with name myubuntu will be created with tag 1 as shown in the above image. Also, you will notice that the image myubuntu:1 and ubuntu:16.04 have the same IMAGE_ID, this is because there were no file structure changes on top of the base image so the new image was tagged with the already existing image.

Now, let's add some more instructions to our dockerfile.

FROM ubuntu:16.04
RUN apt-get update && apt-get install -y tree
RUN touch /tmp/1.txt
RUN touch /tmp/2.txt
RUN touch /tmp/3.txt
RUN touch /tmp/4.txt
RUN touch /tmp/5.txt
RUN touch /tmp/6.txt

The RUN instruction in the above image will install the tree command in our container image and also create some text files in the tmp folder.

RUN instruction is used to execute any command on top of the image and add a new layer on top of existing layers.

Docker daemon executes each instruction in dockerfile one by one and each instruction is executed independently so the previous instruction has no impact on the next one. Each instruction in the dockerfile created a new layer on top of the existing layers.

Before creating the new image let us check the images available currently in our file system.

docker-layered-architecture-and-dockerfiles

So currently there is only 1 image available which ubuntu:14.04.

Now, creating a new image for the above dockerfile instructions.

gaurav@learning-ocean:~/dockerfiles$ docker image build -t myubuntu:1 .
Sending build context to Docker daemon  2.048kB
Step 1/8 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
2e6e20c8e2e6: Pull complete
0551a797c01d: Pull complete
512123a864da: Pull complete
Digest: sha256:43cb19408de1e0ecf3ba5b5372ec98978963d6d0be42d0ad825e77a3bd16b5f7
Status: Downloaded newer image for ubuntu:14.04
 ---> 13b66b487594
Step 2/8 : RUN apt-get update && apt-get install -y tree
 ---> Running in e0cb9be128e5
Get:1 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
Get:2 http://security.ubuntu.com trusty-security/main amd64 Packages [1032 kB]
Get:3 https://esm.ubuntu.com trusty-infra-security InRelease
Get:4 https://esm.ubuntu.com trusty-infra-updates InRelease
Get:5 http://security.ubuntu.com trusty-security/restricted amd64 Packages [18.1 kB]
Get:6 https://esm.ubuntu.com trusty-infra-security/main amd64 Packages
Get:7 http://security.ubuntu.com trusty-security/universe amd64 Packages [378 kB]
Get:8 http://security.ubuntu.com trusty-security/multiverse amd64 Packages [4730 B]
Get:9 https://esm.ubuntu.com trusty-infra-updates/main amd64 Packages
Ign http://archive.ubuntu.com trusty InRelease
Get:10 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:11 http://archive.ubuntu.com trusty-backports InRelease [65.9 kB]
Hit http://archive.ubuntu.com trusty Release.gpg
Fetched 13.9 MB in 32s (430 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
  tree
0 upgraded, 1 newly installed, 0 to remove and 1 not upgraded.
Need to get 37.8 kB of archives.
After this operation, 109 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ trusty/universe tree amd64 1.6.0-1 [37.8 kB]
debconf: unable to initialize frontend: Dialog
debconf: (TERM is not set, so the dialog frontend is not usable.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
dpkg-preconfigure: unable to re-open stdin:
Fetched 37.8 kB in 0s (76.9 kB/s)
Selecting previously unselected package tree.
(Reading database ... 12097 files and directories currently installed.)
Preparing to unpack .../tree_1.6.0-1_amd64.deb ...
Unpacking tree (1.6.0-1) ...
Setting up tree (1.6.0-1) ...
Removing intermediate container e0cb9be128e5
 ---> c0e9cf3cd8c7
Step 3/8 : RUN touch /tmp/1.txt
 ---> Running in 2a6392544c41
Removing intermediate container 2a6392544c41
 ---> 40dc1afe860f
Step 4/8 : RUN touch /tmp/2.txt
 ---> Running in 44657719002f
Removing intermediate container 44657719002f
 ---> 04c9b46c6652
Step 5/8 : RUN touch /tmp/3.txt
 ---> Running in c188f4b2078d
Removing intermediate container c188f4b2078d
 ---> 7174278df979
Step 6/8 : RUN touch /tmp/4.txt
 ---> Running in a250f272ac21
Removing intermediate container a250f272ac21
 ---> a42d4bf321e7
Step 7/8 : RUN touch /tmp/5.txt
 ---> Running in 86f89db53e62
Removing intermediate container 86f89db53e62
 ---> cf7a67141bef
Step 8/8 : RUN touch /tmp/6.txt
 ---> Running in 89ca5baa345e
Removing intermediate container 89ca5baa345e
 ---> 82e9ffb4b1fb
Successfully built 82e9ffb4b1fb
Successfully tagged myubuntu:1
gaurav@learning-ocean:~/dockerfiles$

The new image myubuntu:1 is created.

gaurav@learning-ocean:~$ docker image ls
REPOSITORY                             TAG          IMAGE ID       CREATED         SIZE
myubuntu                               1            82e9ffb4b1fb   4 minutes ago   213MB
ubuntu                                 16.04        065cf14a189c   4 weeks ago     135MB

Now, let's create a container and check if the instructions that we wrote in the dockerfile were executed or not.

gaurav@learning-ocean:~$ docker container run -it myubuntu:1
root@cd35b098c9a7:/# cd /tmp
root@cd35b098c9a7:/tmp# ls
1.txt  2.txt  3.txt  4.txt  5.txt  6.txt
root@cd35b098c9a7:/tmp# tree
.
|-- 1.txt
|-- 2.txt
|-- 3.txt
|-- 4.txt
|-- 5.txt
`-- 6.txt
0 directories, 6 files
root@cd35b098c9a7:/tmp#

On running the container with our new image we can see that the text files that we intended to create using dockerfile have been created in the tmp folder.

Now let us see if any layers have been created for the instructions that were executed.

gaurav@learning-ocean:~$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
myubuntu     1         82e9ffb4b1fb   9 minutes ago   213MB
gaurav@learning-ocean:~$ docker image ls -a
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
myubuntu     1         82e9ffb4b1fb   18 minutes ago   213MB
<none>       <none>    cf7a67141bef   18 minutes ago   213MB
<none>       <none>    a42d4bf321e7   18 minutes ago   213MB
<none>       <none>    7174278df979   18 minutes ago   213MB
<none>       <none>    04c9b46c6652   19 minutes ago   213MB
<none>       <none>    40dc1afe860f   19 minutes ago   213MB
<none>       <none>    c0e9cf3cd8c7   19 minutes ago   213MB
ubuntu       14.04     13b66b487594   3 months ago     197MB
gaurav@learning-ocean:~$

In the above output we can see different images with unique Image ids listed. Starting with our base image which is ubuntu:14.04 with image id "13b66b487594" and the image that we created myubuntu:1 with id "82e9ffb4b1fb". And the other images we see are created as a new layer on the execution of each step of the dockerfile.This can also be verified from the execution logs of the docker build command

For e.g.

Step 3/8 : RUN touch /tmp/1.txt
 ---> Running in 2a6392544c41
Removing intermediate container 2a6392544c41
 ---> 40dc1afe860f

Step 3 is executed in an intermediated container with id 2a6392544c41 which is later removed and the output of this step is created as a new layer with image id 2a6392544c41 which can be seen in our list of images present in the picture above.

Note: It is not a best practice and an efficient way to use RUN multiple times in our dockerfile because it will create as many layers. There is another point to note which is that the docker uses cache for executing commands which have already been executed for better performance.

So, if we modify an existing dockerfile and add a RUN instruction in the middle of the dockerfile instead of adding it to end, this will cause docker to execute each command again as a fresh command and hamper the performance.