Introduction to Dockerfile


Bhaskar S 03/26/2017


Overview

In the article Introduction to Docker, we introduced the basic concepts and the usage of Docker.

Towards the end of the article, we demonstrated how one could create a custom Docker image by manually installing desired application(s) on a base Docker image.

Is there a way to automate this process (in a repeatable way) to build custom Docker images ???

Enter Dockerfile !!!

A Dockerfile is a script with one or more instructions plus argument(s) that allows one to automate building of a custom Docker image from start to finish.

When an image build process is triggered, each instruction in the Dockerfile modifies and commits an intermediate image starting from a specific base Docker image to arrive at the final desired Docker image.

Basics

The following are some of the commonly used instructions in a Dockerfile:

As indicated, the above Dockerfile instructions are the most commonly used ones for one to get started and not the complete list of all the instructions.

Hands-on with Dockerfile

To list all the Docker Images on the local host, execute the following command:

$ docker images

The following could be a typical output:

Output.1

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mycentos            latest              91b1f32a62f3        21 hours ago        273.3 MB
centos              latest              98d35105a391        10 days ago         192.5 MB
ubuntu              latest              0ef2e08ed3fa        3 weeks ago         130 MB

Let us assume the current directory is /home/alice. Let us create a simple Python Flask application called app.py in the current directory as shown below:

app.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return '<br\><h3><center>Hello, Welcome to Dockerized Flask !!!<center></h3>'

if __name__ == '__main__':
    app.run(host='0.0.0.0')

Next, let us create a Dockerfile with instructions to use the base Docker image ubuntu:14.04, run a command to install Python Flask framework, add the Python script app.py to the directory /home/flask, set the working directory to /home/flask, and launch the application on start of the container. The following is the desired Dockerfile:

Dockerfile
FROM ubuntu:14.04

LABEL Version="1.0" \
      Author="Bhaskar.S" \
      Email="bswamina@polarsparc.com"

RUN apt-get update && \
    apt-get install -y python && \
    apt-get install -y python-flask && \
    apt-get clean

ADD app.py /home/flask/app.py

WORKDIR /home/flask

EXPOSE 5000

ENTRYPOINT ["python", "/home/flask/app.py"]

To build a custom Docker image using the above Dockerfile, execute the following command:

$ docker build -t 'ubuntu_flask' .

Notice the use of the -t command line argument to specify the tag name ubuntu_flask.

The following would be a typical output:

Output.2

Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
30d541b48fc0: Already exists
8ecd7f80d390: Already exists
46ec9927bb81: Already exists
2e67a4d67b44: Already exists
7d9dd9155488: Already exists
Digest: sha256:62a5dce5ceccd7f1cb2672a571ebee52cad1f08eec9b57fe4965fb0968a9602e
Status: Downloaded newer image for ubuntu:14.04
 ---> 7c09e61e9035
Step 2 : LABEL Version "1.0" Author "Bhaskar.S" Email "bswamina@polarsparc.com"
 ---> Running in 9157527f86eb
 ---> d134771dc652
Removing intermediate container 9157527f86eb
Step 3 : RUN apt-get update &&     apt-get install -y python &&     apt-get install -y python-flask &&     apt-get clean
 ---> Running in 16f8e70a640b
Ign http://archive.ubuntu.com trusty InRelease
Get:1 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:2 http://archive.ubuntu.com trusty-security InRelease [65.9 kB]
Get:3 http://archive.ubuntu.com trusty Release.gpg [933 B]
Get:4 http://archive.ubuntu.com trusty-updates/main Sources [486 kB]
Get:5 http://archive.ubuntu.com trusty-updates/restricted Sources [6467 B]
Get:6 http://archive.ubuntu.com trusty-updates/universe Sources [222 kB]
Get:7 http://archive.ubuntu.com trusty-updates/main amd64 Packages [1206 kB]
Get:8 http://archive.ubuntu.com trusty-updates/restricted amd64 Packages [21.2 kB]
Get:9 http://archive.ubuntu.com trusty-updates/universe amd64 Packages [519 kB]
Get:10 http://archive.ubuntu.com trusty Release [58.5 kB]
Get:11 http://archive.ubuntu.com trusty-security/main Sources [162 kB]
Get:12 http://archive.ubuntu.com trusty-security/restricted Sources [5066 B]
Get:13 http://archive.ubuntu.com trusty-security/universe Sources [59.8 kB]
Get:14 http://archive.ubuntu.com trusty-security/main amd64 Packages [738 kB]
Get:15 http://archive.ubuntu.com trusty-security/restricted amd64 Packages [17.8 kB]
Get:16 http://archive.ubuntu.com trusty-security/universe amd64 Packages [200 kB]
Get:17 http://archive.ubuntu.com trusty/main Sources [1335 kB]
Get:18 http://archive.ubuntu.com trusty/restricted Sources [5335 B]
Get:19 http://archive.ubuntu.com trusty/universe Sources [7926 kB]
Get:20 http://archive.ubuntu.com trusty/main amd64 Packages [1743 kB]
Get:21 http://archive.ubuntu.com trusty/restricted amd64 Packages [16.0 kB]
Get:22 http://archive.ubuntu.com trusty/universe amd64 Packages [7589 kB]
Fetched 22.4 MB in 7s (3205 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
  libpython-stdlib libpython2.7-minimal libpython2.7-stdlib python-minimal
  python2.7 python2.7-minimal
Suggested packages:
  python-doc python-tk python2.7-doc binutils binfmt-support
The following NEW packages will be installed:
  libpython-stdlib libpython2.7-minimal libpython2.7-stdlib python
  python-minimal python2.7 python2.7-minimal
0 upgraded, 7 newly installed, 0 to remove and 5 not upgraded.
Need to get 3731 kB of archives.
After this operation, 16.0 MB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ trusty-updates/main libpython2.7-minimal amd64 2.7.6-8ubuntu0.3 [307 kB]
Get:2 http://archive.ubuntu.com/ubuntu/ trusty-updates/main python2.7-minimal amd64 2.7.6-8ubuntu0.3 [1187 kB]
Get:3 http://archive.ubuntu.com/ubuntu/ trusty-updates/main libpython2.7-stdlib amd64 2.7.6-8ubuntu0.3 [1873 kB]
Get:4 http://archive.ubuntu.com/ubuntu/ trusty/main libpython-stdlib amd64 2.7.5-5ubuntu3 [7012 B]
Get:5 http://archive.ubuntu.com/ubuntu/ trusty-updates/main python2.7 amd64 2.7.6-8ubuntu0.3 [197 kB]
Get:6 http://archive.ubuntu.com/ubuntu/ trusty/main python-minimal amd64 2.7.5-5ubuntu3 [27.5 kB]
Get:7 http://archive.ubuntu.com/ubuntu/ trusty/main python amd64 2.7.5-5ubuntu3 [134 kB]
Fetched 3731 kB in 1s (2509 kB/s)
Selecting previously unselected package libpython2.7-minimal:amd64.
(Reading database ... 11569 files and directories currently installed.)
Preparing to unpack .../libpython2.7-minimal_2.7.6-8ubuntu0.3_amd64.deb ...
Unpacking libpython2.7-minimal:amd64 (2.7.6-8ubuntu0.3) ...
Selecting previously unselected package python2.7-minimal.
Preparing to unpack .../python2.7-minimal_2.7.6-8ubuntu0.3_amd64.deb ...
Unpacking python2.7-minimal (2.7.6-8ubuntu0.3) ...
Selecting previously unselected package libpython2.7-stdlib:amd64.
Preparing to unpack .../libpython2.7-stdlib_2.7.6-8ubuntu0.3_amd64.deb ...
Unpacking libpython2.7-stdlib:amd64 (2.7.6-8ubuntu0.3) ...
Selecting previously unselected package libpython-stdlib:amd64.
Preparing to unpack .../libpython-stdlib_2.7.5-5ubuntu3_amd64.deb ...
Unpacking libpython-stdlib:amd64 (2.7.5-5ubuntu3) ...
Selecting previously unselected package python2.7.
Preparing to unpack .../python2.7_2.7.6-8ubuntu0.3_amd64.deb ...
Unpacking python2.7 (2.7.6-8ubuntu0.3) ...
Selecting previously unselected package python-minimal.
Preparing to unpack .../python-minimal_2.7.5-5ubuntu3_amd64.deb ...
Unpacking python-minimal (2.7.5-5ubuntu3) ...
Selecting previously unselected package python.
Preparing to unpack .../python_2.7.5-5ubuntu3_amd64.deb ...
Unpacking python (2.7.5-5ubuntu3) ...
Processing triggers for mime-support (3.54ubuntu1.1) ...
Setting up libpython2.7-minimal:amd64 (2.7.6-8ubuntu0.3) ...
Setting up python2.7-minimal (2.7.6-8ubuntu0.3) ...
Linking and byte-compiling packages for runtime python2.7...
Setting up libpython2.7-stdlib:amd64 (2.7.6-8ubuntu0.3) ...
Setting up libpython-stdlib:amd64 (2.7.5-5ubuntu3) ...
Setting up python2.7 (2.7.6-8ubuntu0.3) ...
Setting up python-minimal (2.7.5-5ubuntu3) ...
Setting up python (2.7.5-5ubuntu3) ...
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
  libjs-jquery python-blinker python-itsdangerous python-jinja2
  python-markupsafe python-openssl python-pkg-resources python-pyinotify
  python-werkzeug
Suggested packages:
  javascript-common python-flask-doc python-jinja2-doc python-openssl-doc
  python-openssl-dbg python-distribute python-distribute-doc
  python-pyinotify-doc ipython python-genshi python-lxml python-greenlet
  python-redis python-pylibmc python-memcache python-werkzeug-doc
The following NEW packages will be installed:
  libjs-jquery python-blinker python-flask python-itsdangerous python-jinja2
  python-markupsafe python-openssl python-pkg-resources python-pyinotify
  python-werkzeug
0 upgraded, 10 newly installed, 0 to remove and 5 not upgraded.
Need to get 751 kB of archives.
After this operation, 3894 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ trusty/main libjs-jquery all 1.7.2+dfsg-2ubuntu1 [78.8 kB]
Get:2 http://archive.ubuntu.com/ubuntu/ trusty/main python-blinker all 1.3.dfsg1-1ubuntu2 [29.8 kB]
Get:3 http://archive.ubuntu.com/ubuntu/ trusty-updates/main python-werkzeug all 0.9.4+dfsg-1.1ubuntu2 [236 kB]
Get:4 http://archive.ubuntu.com/ubuntu/ trusty/main python-markupsafe amd64 0.18-1build2 [14.3 kB]
Get:5 http://archive.ubuntu.com/ubuntu/ trusty/main python-jinja2 all 2.7.2-2 [161 kB]
Get:6 http://archive.ubuntu.com/ubuntu/ trusty/main python-itsdangerous all 0.22+dfsg1-1build1 [11.5 kB]
Get:7 http://archive.ubuntu.com/ubuntu/ trusty/main python-flask all 0.10.1-2build1 [51.7 kB]
Get:8 http://archive.ubuntu.com/ubuntu/ trusty/main python-openssl amd64 0.13-2ubuntu6 [81.5 kB]
Get:9 http://archive.ubuntu.com/ubuntu/ trusty-updates/main python-pkg-resources all 3.3-1ubuntu2 [61.9 kB]
Get:10 http://archive.ubuntu.com/ubuntu/ trusty/main python-pyinotify all 0.9.4-1build1 [24.5 kB]
Fetched 751 kB in 1s (620 kB/s)
Selecting previously unselected package libjs-jquery.
(Reading database ... 12368 files and directories currently installed.)
Preparing to unpack .../libjs-jquery_1.7.2+dfsg-2ubuntu1_all.deb ...
Unpacking libjs-jquery (1.7.2+dfsg-2ubuntu1) ...
Selecting previously unselected package python-blinker.
Preparing to unpack .../python-blinker_1.3.dfsg1-1ubuntu2_all.deb ...
Unpacking python-blinker (1.3.dfsg1-1ubuntu2) ...
Selecting previously unselected package python-werkzeug.
Preparing to unpack .../python-werkzeug_0.9.4+dfsg-1.1ubuntu2_all.deb ...
Unpacking python-werkzeug (0.9.4+dfsg-1.1ubuntu2) ...
Selecting previously unselected package python-markupsafe.
Preparing to unpack .../python-markupsafe_0.18-1build2_amd64.deb ...
Unpacking python-markupsafe (0.18-1build2) ...
Selecting previously unselected package python-jinja2.
Preparing to unpack .../python-jinja2_2.7.2-2_all.deb ...
Unpacking python-jinja2 (2.7.2-2) ...
Selecting previously unselected package python-itsdangerous.
Preparing to unpack .../python-itsdangerous_0.22+dfsg1-1build1_all.deb ...
Unpacking python-itsdangerous (0.22+dfsg1-1build1) ...
Selecting previously unselected package python-flask.
Preparing to unpack .../python-flask_0.10.1-2build1_all.deb ...
Unpacking python-flask (0.10.1-2build1) ...
Selecting previously unselected package python-openssl.
Preparing to unpack .../python-openssl_0.13-2ubuntu6_amd64.deb ...
Unpacking python-openssl (0.13-2ubuntu6) ...
Selecting previously unselected package python-pkg-resources.
Preparing to unpack .../python-pkg-resources_3.3-1ubuntu2_all.deb ...
Unpacking python-pkg-resources (3.3-1ubuntu2) ...
Selecting previously unselected package python-pyinotify.
Preparing to unpack .../python-pyinotify_0.9.4-1build1_all.deb ...
Unpacking python-pyinotify (0.9.4-1build1) ...
Setting up libjs-jquery (1.7.2+dfsg-2ubuntu1) ...
Setting up python-blinker (1.3.dfsg1-1ubuntu2) ...
Setting up python-werkzeug (0.9.4+dfsg-1.1ubuntu2) ...
Setting up python-markupsafe (0.18-1build2) ...
Setting up python-jinja2 (2.7.2-2) ...
Setting up python-itsdangerous (0.22+dfsg1-1build1) ...
Setting up python-flask (0.10.1-2build1) ...
Setting up python-openssl (0.13-2ubuntu6) ...
Setting up python-pkg-resources (3.3-1ubuntu2) ...
Setting up python-pyinotify (0.9.4-1build1) ...
 ---> 3fd072b487a3
Removing intermediate container 16f8e70a640b
Step 4 : ADD app.py /home/flask/app.py
 ---> c89622a42fae
Removing intermediate container 8b26773ba03e
Step 5 : WORKDIR /home/flask
 ---> Running in 09102acfdf58
 ---> eac1b6e320b2
Removing intermediate container 09102acfdf58
Step 6 : EXPOSE 5000
 ---> Running in 07705e9ddf24
 ---> 5b45c50c7e0e
Removing intermediate container 07705e9ddf24
Step 7 : ENTRYPOINT python /home/flask/app.py
 ---> Running in c91f95f5a10e
 ---> 54f0fa012cd1
Removing intermediate container c91f95f5a10e
Successfully built 54f0fa012cd1

Now, execute the following command:

$ docker images

The following would be a typical output:

Output.3

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu_flask        latest              54f0fa012cd1        19 seconds ago      239.8 MB
mycentos            latest              91b1f32a62f3        21 hours ago        273.3 MB
centos              latest              98d35105a391        10 days ago         192.5 MB
ubuntu              latest              0ef2e08ed3fa        3 weeks ago         130 MB
ubuntu              14.04               7c09e61e9035        3 weeks ago         188 MB

From the above Output.3, we see our newly built ubuntu_flask image.

To fetch a detailed information on the newly created ubuntu_flask image, execute the following command:

$ docker inspect ubuntu_flask

The following would be a typical output:

Output.4

[
    {
        "Id": "sha256:54f0fa012cd110d547e568dd4b2cf0dc5eafdba5c708fefae54f16d065cf6650",
        "RepoTags": [
            "ubuntu_flask:latest"
        ],
        "RepoDigests": [],
        "Parent": "sha256:5b45c50c7e0e80848f783f9d1e0df4d3d4e000ef5bac101abf61fd557d25983b",
        "Comment": "",
        "Created": "2017-03-26T18:57:17.645109888Z",
        "Container": "c91f95f5a10e1fc952ac370a9c256bd57f70d6cd75ad6b0b47237edbb8425ea0",
        "ContainerConfig": {
            "Hostname": "ae9df72d0f92",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "5000/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "ENTRYPOINT [\"python\" \"/home/flask/app.py\"]"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:5b45c50c7e0e80848f783f9d1e0df4d3d4e000ef5bac101abf61fd557d25983b",
            "Volumes": null,
            "WorkingDir": "/home/flask",
            "Entrypoint": [
                "python",
                "/home/flask/app.py"
            ],
            "OnBuild": [],
            "Labels": {
                "Author": "Bhaskar.S",
                "Email": "bswamina@polarsparc.com",
                "Version": "1.0"
            }
        },
        "DockerVersion": "1.12.1",
        "Author": "",
        "Config": {
            "Hostname": "ae9df72d0f92",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "5000/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": null,
            "ArgsEscaped": true,
            "Image": "sha256:5b45c50c7e0e80848f783f9d1e0df4d3d4e000ef5bac101abf61fd557d25983b",
            "Volumes": null,
            "WorkingDir": "/home/flask",
            "Entrypoint": [
                "python",
                "/home/flask/app.py"
            ],
            "OnBuild": [],
            "Labels": {
                "Author": "Bhaskar.S",
                "Email": "bswamina@polarsparc.com",
                "Version": "1.0"
            }
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 239835803,
        "VirtualSize": 239835803,
        "GraphDriver": {
            "Name": "aufs",
            "Data": null
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:c29b5eadf94a90a2abda13e765d4fad4825fd15621dea1d9a98b60b89b835c2a",
                "sha256:04ab82f865cf3b278ed27279067e57c8bcc0b9e7fdaffb474398ee803c0ca4cb",
                "sha256:9bd4c7af882add630050020f9e9b0c4683218cb26054f955f82bcd8db051cf00",
                "sha256:af43131c4039e254044a9cf939e67220fac18ce20ad4bec3780e3d487e362497",
                "sha256:bd00cdbae64103e3da98764ef17e8fbe12a8fb71a6ba148a3d737d8e4855665a",
                "sha256:025e03bc82fa20febc453e831df18501f3ea36013844235cb99686241272818d",
                "sha256:a72b872b6e3034eccd64994b575f40d625a2d99bc70f0a087488b343316af97f"
            ]
        }
    }
]

To display the history of all the changes from the base Docker image to the newly created ubuntu_flask image, execute the following command:

$ docker history ubuntu_flask

The following would be a typical output:

Output.5

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
54f0fa012cd1        17 minutes ago      /bin/sh -c #(nop)  ENTRYPOINT ["python" "/hom   0 B
5b45c50c7e0e        17 minutes ago      /bin/sh -c #(nop)  EXPOSE 5000/tcp              0 B
eac1b6e320b2        17 minutes ago      /bin/sh -c #(nop)  WORKDIR /home/flask          0 B
c89622a42fae        17 minutes ago      /bin/sh -c #(nop) ADD file:8a4dddac16e7401900   216 B
3fd072b487a3        17 minutes ago      /bin/sh -c apt-get update &&     apt-get inst   51.87 MB
d134771dc652        18 minutes ago      /bin/sh -c #(nop)  LABEL Version=1.0 Author=B   0 B
7c09e61e9035        3 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0 B
<missing>           3 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'doc   7 B
<missing>           3 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kB
<missing>           3 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0 B
<missing>           3 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /u   194.6 kB
<missing>           3 weeks ago         /bin/sh -c #(nop) ADD file:a642bdc2d8d6e4484e   187.8 MB

The output starts with the change from the last instruction and works backwards to the base image.

To create and launch a Docker container on the local host from the newly created ubuntu_flask image, execute the following command:

$ docker run -d ubuntu_flask

Notice the use of the command line option -d in the command above.

The -d option indicates we want to run the container in the background as a daemon.

The following would be a typical output:

Output.6

bbb258287cd12874a9bc1338ea596a1c7b1c6cbf329a270db92f4b40372b1f31

Now, launch a browser on the local host and access the URL http://127.0.0.1:5000. The following would be a typical view:

Docker Flask Failure
Figure-1

WHAT ??? We are not able to access the Python Flask application running in the Docker container from the local host.

This is the default behavior of the firewall on the local host as there are no network routes defined to the Docker container.

Stop the Docker container and re-launch it by executing the following command:

$ docker run -p 5000 -d ubuntu_flask

Notice the use of the command line option -p 5000 in the command above.

The -p 5000 option maps the network port 5000 on the container to a random network port on the local host.

To display info about the running Docker container(s), execute the following command:

$ docker ps

The following would be a typical output:

Output.7

CONTAINER ID    IMAGE           COMMAND                  CREATED         STATUS          PORTS                     NAMES
cb04482ba656    ubuntu_flask    "python /home/flask/a"   5 minutes ago   Up 5 minutes    0.0.0.0:32768->5000/tcp   kickass_murdock

From the Output.7 above, we see that the network port 5000 on the container is mapped to the network port 32768 on the local host.

Now, re-launch a browser on the local host and access the URL http://127.0.0.1:32768. The following would be a typical view:

Docker Flask Success
Figure-2

If we desire to bridge the network port 5000 on the container to the same network port 5000 on the local host, execute the following command:

$ docker run -p 5000:5000 -d ubuntu_flask

The -p 5000:5000 option bridges the network port 5000 on the container (the port after the colon) to the network port 5000 on the local host (the port before the colon).

The general syntax of the -p option is: -p <host-port>:<container-port>

To list all the running Docker containers, execute the following command:

$ docker ps

The following would be a typical output:

Output.8

CONTAINER ID    IMAGE           COMMAND                  CREATED         STATUS          PORTS                    NAMES
eb67f26764ea    ubuntu_flask    "python /home/flask/a"   8 minutes ago   Up 8 minutes    0.0.0.0:5000->5000/tcp   admiring_archimedes

Open a browser on the local host and access the URL http://127.0.0.1:5000. The following would be a typical view:

Docker Flask 5000
Figure-3

BINGO !!! we have successfully created a custom Docker image with a desired application and launched it as a Docker container on the local host.

References

Introduction to Docker

Official Docker Documentation