8 min read

Docker images are read-only templates. They give us containers during runtime. Central to this is the concept of a ‘base image’. Layers then sit on top of this base image. For example, you might have a base image of Fedora or Ubuntu, but you can then install packages or make modifications over the base image to create a new layer. The base image and new layer can then be treated as a completely  new image.

In the image below, Debian is the base image and emacs and Apache are the two layers added on top of it. They are highly portable and can be shared easily:

Docker images

Source: Docker Image layers

Layers are transparently laid on top of the base image to create a single coherent filesystem.

There are a couple of ways to create images, one is by manually committing layers and the other way is through Dockerfiles. In this recipe, we’ll create images with Dockerfiles.

Dockerfiles help us in automating image creation and getting precisely the same image every time we want it. The Docker builder reads instructions from a text file (a Dockerfile) and executes them one after the other in order. It can be compared as Vagrant files, which allows you to configure VMs in a predictable manner.

Getting ready

A Dockerfile with build instructions.

Create an empty directory:

$ mkdir sample_image
$ cd sample_image

Create a file named Dockerfile with the following content:

$ cat Dockerfile
# Pick up the base image 
FROM fedora 
# Add author name 
MAINTAINER Neependra Khare 
# Add the command to run at the start of container 
CMD date

How to do it…

  • Run the following command inside the directory, where we created Dockerfile to build the image:
$ docker build .

docker build

  • We did not specify any repository or tag name while building the image. We can give those with the -toption as follows:
$ docker build -t fedora/test .

docker build

The preceding output is different from what we did earlier. However, here we are using a cache after each instruction. Docker tries to save the intermediate images as we saw earlier and tries to use them in subsequent builds to accelerate the build process. If you don’t want to cache the intermediate images, then add the –no-cache option with the build. Let’s take a look at the available images now:

How it works…

A context defines the files used to build the Docker image. In the preceding command, we define the context to the build. The build is done by the Docker daemon and the entire context is transferred to the daemon. This is why we see the Sending build context to Docker daemon 2.048 kB message. If there is a file named .dockerignore in the current working directory with the list of files and directories (new line separated), then those files and directories will be ignored by the build context. More details about .dockerignore can be found at https://docs.docker.com/reference/builder/#the-dockerignore-file.

After executing each instruction, Docker commits the intermediate image and runs a container with it for the next instruction. After the next instruction has run, Docker will again commit the container to create the intermediate image and remove the intermediate container created in the previous step.

For example, in the preceding screenshot, eb9f10384509 is an intermediate image and c5d4dd2b3db9 and ffb9303ab124 are the intermediate containers. After the last instruction is executed, the final image will be created. In this case, the final image is 4778dd1f1a7a:

docker images

The -a option can be specified with the docker images command to look for intermediate layers:

$ docker images -a

There’s more…

The format of the Dockerfile is:

INSTRUCTION arguments

Generally, instructions are given in uppercase, but they are not case sensitive. They are evaluated in order. A # at the beginning is treated like a comment.

Let’s take a look at the different types of instructions:

  • FROM: This must be the first instruction of any Dockerfile, which sets the base image for subsequent instructions. By default, the latest tag is assumed to be:
FROM  <image>

Alternatively, consider the following tag:

FROM  <images>:<tag>

There can be more than one FROM instruction in one Dockerfile to create multiple images. If only image names, such as Fedora and Ubuntu are given, then the images will be downloaded from the default Docker registry (Docker Hub). If you want to use private or third-party images, then you have to mention this as follows: 

[registry_hostname[:port]/][user_name/](repository_name:version_tag)

Here is an example using the preceding syntax:

FROM registry-host:5000/nkhare/f20:httpd

MAINTAINER: This sets the author for the generated image, MAINTAINER <name>.

RUN: We can execute the RUN instruction in two ways—first, run in the shell (sh -c):

RUN <command> <param1> ... <pamamN>

Second, directly run an executable:

RUN ["executable", "param1",...,"paramN" ]

As we know with Docker, we create an overlay—a layer on top of another layer—to make the resulting image. Through each RUN instruction, we create and commit a layer on top of the earlier committed layer. A container can be started from any of the committed layers.

By default, Docker tries to cache the layers committed by different RUN instructions, so that it can be used in subsequent builds. However, this behavior can be turned off using –no-cache flag while building the image.

LABEL: Docker 1.6 added a new feature to the attached arbitrary key-value pair to Docker images and containers. We covered part of this in the Labeling and filtering containers recipe in Chapter 2, Working with Docker Containers. To give a label to an image, we use the LABEL instruction in the Dockerfile as LABEL distro=fedora21.

CMD: The CMD instruction provides a default executable while starting a container. If the CMD instruction does not have an executable (parameter 2), then it will provide arguments to ENTRYPOINT.

CMD  ["executable", "param1",...,"paramN" ]
CMD ["param1", ... , "paramN"]
CMD <command> <param1> ... <pamamN>

Only one CMD instruction is allowed in a Dockerfile. If more than one is specified, then only the last one will be honored.

ENTRYPOINT: This helps us configure the container as an executable. Similar to CMD, there can be at max one instruction for ENTRYPOINT; if more than one is specified, then only the last one will be honored:

ENTRYPOINT  ["executable", "param1",...,"paramN" ]
ENTRYPOINT <command> <param1> ... <pamamN>

Once the parameters are defined with the ENTRYPOINT instruction, they cannot be overwritten at runtime. However, ENTRYPOINT can be used as CMD, if we want to use different parameters to ENTRYPOINT.

EXPOSE: This exposes the network ports on the container on which it will listen at runtime:

EXPOSE  <port> [<port> ... ]

We can also expose a port while starting the container. We covered this in the Exposing a port while starting a container recipe in Chapter 2, Working with Docker Containers.

ENV: This will set the environment variable <key> to <value>. It will be passed all the future instructions and will persist when a container is run from the resulting image:

ENV <key> <value>

ADD: This copies files from the source to the destination:

ADD <src> <dest>

The following one is for the path containing white spaces:

ADD ["<src>"... "<dest>"]
    • <src>: This must be the file or directory inside the build directory from which we are building an image, which is also called the context of the build. A source can be a remote URL as well.
    • <dest>: This must be the absolute path inside the container in which the files/directories from the source will be copied.

COPY: This is similar to ADD.COPY <src> <dest>:

COPY  ["<src>"... "<dest>"]

VOLUME: This instruction will create a mount point with the given name and flag it as mounting the external volume using the following syntax:

VOLUME ["/data"]

Alternatively, you can use the following code:

VOLUME /data

USER: This sets the username for any of the following run instructions using the following syntax:

USER  <username>/<UID>

WORKDIR: This sets the working directory for the RUN, CMD, and ENTRYPOINT instructions that follow it. It can have multiple entries in the same Dockerfile. A relative path can be given which will be relative to the earlier WORKDIR instruction using the following syntax:

WORKDIR <PATH>

ONBUILD: This adds trigger instructions to the image that will be executed later, when this image will be used as the base image of another image. This trigger will run as part of the FROM instruction in downstream Dockerfile using the following syntax:

ONBUILD [INSTRUCTION]

See also

Look at the help option of docker build:

$ docker build -help

The documentation on the Docker website https://docs.docker.com/reference/builder/

You just enjoyed an excerpt from the book, DevOps: Puppet, Docker, and Kubernetes by Thomas Uphill, John Arundel, Neependra Khare, Hideto Saito, Hui-Chuan Chloe Lee, and Ke-Jou Carol Hsu. To master working with Docker containers, images and much more, check out this book today!

Read other posts:

How to publish Docker and integrate with Maven

Building Scalable Microservices

How to deploy RethinkDB using Docker

DevOps Puppet Docker and Kubernetes

 

Managing Editor, Packt Hub. Former mainframes/DB2 programmer turned marketer/market researcher turned editor. I love learning, writing and tinkering when I am not busy running after my toddler. Wonder how algorithms would classify this!

LEAVE A REPLY

Please enter your comment!
Please enter your name here