Dockerfile is used to build your own Docker images. A docker file can contain a sequence of instructions such as,
–what base OS to use?
–what files to download?
–what commands to run?
–what service to start? etc, so that the image is built to be run as per requirements.


Listed below are the different Dockerfile instructions that is commonly needed to build a Docker image.

A docker image is some what similar to a Linux systemd service. For instance when you start a systemd service with ‘systemctl start servicename’, the systemd daemon looks for the script that was defined to run when the service is started. If the script returns a non-zero value, systemd reports a “failed” status for the service.

Similarly when you build an image with docker, let’s say you have specified an invalid URL for a file download or may be an invalid command, this returns a non-zero exit value. The docker build fails when a non zero exit value is returned by one of its instructions.

Now that we have a good understanding of how a Dockerfile functions, lets look at the different sections of a Dockerfile that defines the flow of building a docker image.

FROM: Docker base image

FROM – Select the base image to build the new image on top of.

Lets say you want your application to run with a centos base image, you begin the docker file with the below lines to choose a centos 7 base image.

#Download base image centos 7
FROM centos:7

Shell and Exec method for commands

There are two methods when you are trying to execute a command in Dockerfile, which are the shell method and the exec method.
shell method syntax:

'instruction' 'command'

exec method syntax:

'instruction' ["executable", "param1", "param2", ...]

Further, ‘instruction’ can be of three types namely RUN, CMD, ENTRYPOINT.

These three types of instructions are basically used to execute a command. We will look at when a specific type of instruction will be used later. Lets just look at the two methods of using these instructions first, and then we will look at the instructions itself later.

Regardless of the type of instruction, when shell method is used as in the below example,
‘instruction’ echo hello
The command that runs in the background, is run by invoking the default shell, which is,

/bin/sh -c "echo hello"

Whereas if the exec method is used, as in the below example,
‘instruction’ [“/bin/echo”, “Hello world”]
a shell is not invoked.

/bin/echo hello

You can declare variables in a docker file with ENV, as in below example.

ENV mystring Hello world.

When we instruct the command to be run in shell method , as in the below example,
‘instruction’ echo $mystring
It produces the below output.

Hello world

The variable is successfully substituted with its value.

Whereas when exec method is used,
‘instruction’ [“echo”, “$mystring”]
It produces the below output.

echo $mystring

The variable is not substituted with its value.

To work around this, we can invoke a shell by calling it in exec method, as in below example.
‘instruction’ [“/bin/bash”, “-c”, “echo $mystring”]

Now that we have seen the two methods of executing a command, we will look at the three types of command instructions RUN,CMD & ENTRYPOINT, to understand the difference between them.

RUN vs CMD vs ENTRYPOINT command instructions

Lets understand the difference between and RUN and CMD first. RUN and CMD are instructions used to execute commands, but the fact that makes these two different is that any commands passed to RUN instrution is executed at the time of image build, whereas any commands passed to CMD instruction is executed when a container is started.

For example, you would want to execute commands such as ‘yum install’ or ‘mkdir’ at the time of image build only and not everytime a container is started. hence these commands can be execeuted with RUN.

#Download base image centos 7
FROM centos:7

#shell method
RUN yum install httpd 

#exec method
RUN ["yum", "install", "httpd"]

#Running multiple commands with RUN instruction
#The RUN instruction keyword can appear seperately as well in individual lines, unlike below.
RUN yum -y update && \
    yum -y install httpd && \
    yum clean all

And commands to start service etc, needs to run at the time the container is started and not when the image is built.
#CMD instruction example, this is executed only at run time not at build time, i.e when the container is started	
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]

Now that we understand, RUN is for build-time commands and CMD is for container-start-time or run-time commands, lets look at ENTRYPOINT.
ENTRYPOINT is very similar to CMD and has the same purpose as CMD, but the key difference is in the below definitions.

ENTRYPOINT: can be used to specify command to run when container starts.
CMD: can be used to specify command to run when container starts OR it can also be used to specify an argument which will get passed to an ENTRYPOINT command.

Lets look some examples to understand the purpose and difference between CMD and ENTRYPOINT.
If you have ever tried running a base image such as Centos or Ubuntu, with ‘docker run centos’, the container goes into an exited state immediately unless you start an interactive shell with ‘docker run -it centos’. This is because for most of the base images, /bin/bash or /bin/sh is set as the default ENTRYPOINT or CMD. When the container is run, this runs once and exits.

docker run docker.io/centos
docker ps

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Thus an ENTRYPOINT/CMD is any command that is started first inside the container when the container is run. This command has pid 1. docker container stops running or goes into an exited state when pid 1 no longer exists.

Lets look at a CMD example.

cat Dockerfile

#Download base image centos 7
FROM centos:7

CMD ping www.google.com

Lets build an image with this dockerfile.

Docker Build syntax

docker build [path/to/dockerfile]
# ‘-t test_image’, names the image that is going to build as ‘test_image’

docker build -t test_image images/

Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM centos:7
 ---> 49f7960eb7e4
Step 2/2 : ENTRYPOINT ping www.google.com
 ---> Using cache
 ---> 677cf5ac77a7
Successfully built 677cf5ac77a7

docker images

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
test_image              latest              36fdbd610afd        31 minutes ago      200 MB
docker.io/centos        latest              e934aafc2206        2 months ago        199 MB

We now see the newly build image, test_image getting listed.
Lets try running the image.

docker run test_image

PING www.google.com (74.125.197.105) 56(84) bytes of data.
64 bytes from 74.125.197.105 (74.125.197.105): icmp_seq=1 ttl=39 time=22.7 ms
64 bytes from 74.125.197.105 (74.125.197.105): icmp_seq=2 ttl=39 time=22.8 ms
64 bytes from 74.125.197.105 (74.125.197.105): icmp_seq=3 ttl=39 time=22.7 ms
64 bytes from 74.125.197.105 (74.125.197.105): icmp_seq=4 ttl=39 time=22.7 ms
64 bytes from 74.125.197.105 (74.125.197.105): icmp_seq=5 ttl=39 time=22.7 ms
^C
--- www.google.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4002ms
rtt min/avg/max/mdev = 22.750/22.777/22.815/0.098 ms

docker ps

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

We see the command speficied as ENTRYPOINT in docker file starts running immediately. Once we kill, the docker container is not running anymore.
Lets start the container again with in a detached mode and then lets list all processess running in the background.

docker run -d test_image /bin/bash

6fb2ba0118835808aa169919fe437b7bb8321b95824ed54249c58a7e3f0f034b

docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
6fb2ba011883        test_image          "/bin/sh -c 'ping ..."   30 seconds ago      Up 29 seconds                           sharp_swartz

docker exec 6fb2ba011883 ps -ef

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 13:47 ?        00:00:00 ping www.google.com
root          9      0  0 13:49 ?        00:00:00 ps -ef

We see that the entry point command, ‘ping www.google.com’ has taken the pid 1.

NOTE: replacing CMD with ENTRYPOINT would produce the same exact result.

From the boxed definition, we know that commands passed to CMD becomes arguments to commands passed to ENTRYPOINT, when both CMD and ENTRYPOINT exists in Dockefile. Lets look at an example of using CMD as an argument to an ENTRYPOINT.
NOTE: Passing CMD as an argument to ENTRYPOINT works only with exec method

cat Dockerfile

FROM centos:7
ENTRYPOINT ["/bin/ping"]
CMD ["yahoo.com"]

Running the image without any argument will ping the yahoo.com:

docker run -it test_image

PING yahoo.com (72.30.35.10): 48 data bytes
56 bytes from 72.30.35.10: icmp_seq=0 ttl=64 time=0.096 ms
56 bytes from 72.30.35.10: icmp_seq=1 ttl=64 time=0.088 ms
56 bytes from 72.30.35.10: icmp_seq=2 ttl=64 time=0.088 ms
^C--- localhost ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.088/0.091/0.096/0.000 ms
Now, running the image with an argument will ping the argument:

Now, lets pass an argument at run time.

docker run -it test_image google.com

PING google.com (173.194.45.70): 48 data bytes
56 bytes from 173.194.45.70: icmp_seq=0 ttl=55 time=32.583 ms
56 bytes from 173.194.45.70: icmp_seq=2 ttl=55 time=30.327 ms
56 bytes from 173.194.45.70: icmp_seq=4 ttl=55 time=46.379 ms
^C--- google.com ping statistics ---
5 packets transmitted, 3 packets received, 40% packet loss
round-trip min/avg/max/stddev = 30.327/36.430/46.379/7.095 ms

We see that the run time argument google.com is pinging instead of the defaults from docker file. Thus we now know that the default argument can be overridden using docker run command at run time.

  
NOTE: the RUN instruction keyword can appear mulltiple times in dockerfile whereas the CMD and ENTRYPOINT instruction keyword can appear only ones.

USER: default user

USER – Define the default User all commands will be run as within any Container created from your Image. It can be either a UID or username

USER admin

VOLUME: volumes inside docker

VOLUME – Creates a mount point within the Container and links it back to file systems accessible by the Docker Host. New Volumes get populated with the pre-existing contents of the specified location in the image. It is not recommended to define Volumes in a Dockerfile. Volumes should be managed with docker-compose or “docker run” commands. Due to technical limitations path of volume in host cannot be mapped to a path in host through Dockerfile, however can be done with docker run command.

VOLUME /var/www/html

Syntax to map a container volume to a path in host is as follows.
docker run -v /host/path:/container/path

ADD: copy files into a docker image

ADD – allows you to use a URL or a path to a file from Docker host as the source of a file to be copied to a path in container. When a URL is provided, a file is downloaded from the URL and copied to the destination specified in container.

ADD http://example.com/image1 /tmp/image1
ADD /data/image2 /tmp/image2

WORKDIR: default working directory for commands

WORKDIR – Define the default working directory for the command defined in the “ENTRYPOINT” or “CMD” instructions

WORKDIR /home

EXPOSE: open docker ports

EXPOSE – Define which Container ports to expose

EXPOSE 80

MAINTAINER: sets the Author field of the generated images

MAINTAINER – Optional field to let you identify yourself as the maintainer of this image

MAINTAINER UnixUtils "admin@unixutils.com"

Simple Dockerfile example for a httpd application image

cat Dockerfile

#use centos base image
FROM centos:7

#default user to run commands
USER root

#default working directory to run commands
WORKDIR /root/

#install httpd
RUN ["yum", "install", "httpd"]

#share /var/www/html on container with host
VOLUME /var/www/html

#open port 80 on container
EXPOSE 80

#start httpd when the container is started
CMD ["-D", "FOREGROUND"]
ENTRYPOINT ["/usr/sbin/httpd"]

#sets the Author field of the generated images 
MAINTAINER UnixUtils "admin@unixutils.com"