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:
FROM :: this *must* be the first instruction in the Dockerfile.
It uses the syntax:
FROM <image>[:<tag>]
where, <image> is the base Docker image to use. The optional [:<tag>] indicates a specific version of the image
LABEL :: this instruction allows one to include meta-data in the resulting Docker image.
It uses the syntax:
LABEL <key>="<value>" \
<key-2>="<value-2>" \
<key-3>="<value-3>" \
... \
<key-n>="<value-n>"
where, <key> is the name of the meta-data and "<value>" is any text value in quotes.
If there are multiple key="value" pairs to be included, they can be in a separate line ending with a backslash as shown above
ENV :: this instruction allows one to set and persist environment variables in the resulting Docker image. Instructions following this can reference the set environment variables if needed.
It uses the syntax:
ENV <key>="<value>" \
<key-2>="<value-2>" \
<key-3>="<value-3>" \
... \
<key-n>="<value-n>"
where, <key> is the name of the environment variable and "<value>" is a desired value in quotes.
If there are multiple key=value pairs to be included, they can be in a separate line ending with a backslash as shown above
RUN :: this instruction will execute the specified command(s) in the current Docker image and commit the changes to a resulting Docker image.
It uses the syntax:
RUN <command> \
<command-2> \
<command-3> \
... \
<command-n>
where, <command> is any script or executable that will be executed by the default Linux shell (/bin/sh).
If there are multiple commands to be executed, they can be in a separate line ending with a backslash as shown above
ADD :: this instruction will copy files/directories from a specified source to the specified destination on the resulting Docker image.
It uses the syntax:
ADD <source> <destination>
where, <source> is a location on the host from where the image is being built and <destination> is a location on the resulting Docker image
EXPOSE :: this instruction informs that the application on this image will listen on the specified network port.
It uses the syntax:
EXPOSE <port>
where, <port> is a network port on the resulting Docker image
ENTRYPOINT :: this instruction allows an executable to be run when the container with the resulting Docker image is started.
It uses the syntax:
ENTRYPOINT ["<executable>", "<arg-1>", "<arg-2>", ..., "<arg-n>"]
where, <executable> is the executable to be run and "<arg-1>" through "<arg-n>" the arguments to the executable
WORKDIR :: this instruction sets the working directory for any of the following instructions, such as, RUN, ADD, or ENTRYPOINT.
It uses the syntax:
WORKDIR <path>
where, <path> is the full directory path on the resulting Docker image
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:
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:
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:
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:
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:
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:
[ { "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:
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:
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:
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:
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:
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:
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:
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