Dockerfile

We have seen how to create images by committing containers. What if you want to update the image with new versions of dependencies or new versions of your own application? It soon becomes impractical to do the steps of starting, setting up, and committing over and over again. We need a repeatable method to build images. In comes Dockerfile, which is nothing more than a text file that contains instructions to automate the steps you would otherwise have taken to build an image. docker build will read these instructions sequentially, committing them along the way, and build an image.

The docker build command takes this Dockerfile and a context to execute the instructions, and builds a Docker image. Context refers to the path or source code repository URL given to the docker build command.

A Dockerfile contains instructions in this format:

# Comment
INSTRUCTION arguments

Any line beginning with # will be considered as a comment. If a # sign is present anywhere else, it will be considered a part of arguments. The instruction is not case-sensitive, although it is an accepted convention for instructions to be uppercase so as to distinguish them from the arguments.

Let's look at the instructions that we can use in a Dockerfile.

The FROM instruction

The FROM instruction sets the base image for the subsequent instructions. A valid Dockerfile's first non-comment line will be a FROM instruction:

FROM <image>:<tag>

The image can be any valid local or public image. If it is not found locally,the Docker build command will try to pull it from the public registry. The tag command is optional here. If it is not given, the latest command is assumed. If the incorrect tag command is given, it returns an error.

The MAINTAINER instruction

The MAINTAINER instruction allows you to set the author for the generated images:

MAINTAINER <name>

The RUN instruction

The RUN instruction will execute any command in a new layer on top of the current image, and commit this image. The image thus committed will be used for the next instruction in the Dockerfile.

The RUN instruction has two forms:

  • The RUN <command> form
  • The RUN ["executable", "arg1", "arg2"...] form

In the first form, the command is run in a shell, specifically the /bin/sh -c <command> shell. The second form is useful in instances where the base image doesn't have a /bin/sh shell. Docker uses a cache for these image builds. So in case your image build fails somewhere in the middle, the next run will reuse the previously successful partial builds and continue from the point where it failed.

The cache will be invalidated in these situations:

  • When the docker build command is run with the --no-cache flag.
  • When a non-cacheable command such as apt-get update is given. All the following RUN instructions will be run again.
  • When the first encountered ADD instruction will invalidate the cache for all the following instructions from the Dockerfile if the contents of the context have changed. This will also invalidate the cache for the RUN instructions.

The CMD instruction

The CMD instruction provides the default command for a container to execute. It has the following forms:

  • The CMD ["executable", "arg1", "arg2"...] form
  • The CMD ["arg1", "arg2"...] form
  • The CMD command arg1 arg2 … form

The first form is like an exec and it is the preferred form, where the first value is the path to the executable and is followed by the arguments to it.

The second form omits the executable but requires the ENTRYPOINT instruction to specify the executable.

If you use the shell form of the CMD instruction, then the <command> command will execute in the /bin/sh -c shell.

Note

If the user provides a command in docker run, it overrides the CMD command.

The difference between the RUN and CMD instructions is that a RUN instruction actually runs the command and commits it, whereas the CMD instruction is not executed during build time. It is a default command to be run when the user starts a container, unless the user provides a command to start it with.

For example, let's write a Dockerfile that brings a Star Wars output to your terminal:

FROM ubuntu:14.04
MAINTAINER shrikrishna
RUN apt-get -y install telnet
CMD ["/usr/bin/telnet", "towel.blinkenlights.nl"]

Save this in a folder named star_wars and open your terminal at this location. Then run this command:

$ docker build -t starwars .

Now you can run it using the following command:

$ docker run -it starwars 

The following screenshot shows the starwars output:

The CMD instruction

Thus, you can watch Star Wars in your terminal!

Note

This Star Wars tribute was created by Simon Jansen, Sten Spans, and Mike Edwards. When you've had enough, hold Ctrl + ]. You will be given a prompt where you can type close to exit.

The ENTRYPOINT instruction

The ENTRYPOINT instruction allows you to turn your Docker image into an executable. In other words, when you specify an executable in an ENTRYPOINT, containers will run as if it was just that executable.

The ENTRYPOINT instruction has two forms:

  1. The ENTRYPOINT ["executable", "arg1", "arg2"...] form.
  2. The ENTRYPOINT command arg1 arg2 … form.

This instruction adds an entry command that will not be overridden when arguments are passed to the docker run command, unlike the behavior of the CMD instruction. This allows arguments to be passed to the ENTRYPOINT instruction. The docker run <image> -arg command will pass the -arg argument to the command specified in the ENTRYPOINT instruction.

Parameters, if specified in the ENTRYPOINT instruction, will not be overridden by the docker run arguments, but parameters specified via the CMD instruction will be overridden.

As an example, let's write a Dockerfile with cowsay as the ENTRYPOINT instruction:

Note

The cowsay is a program that generates ASCII pictures of a cow with a message. It can also generate pictures using premade images of other animals, such as Tux the Penguin, the Linux mascot.

FROM ubuntu:14.04
RUN apt-get -y install cowsay
ENTRYPOINT ["/usr/games/cowsay"]
CMD ["Docker is so awesomoooooooo!"]

Save this with the name Dockerfile in a folder named cowsay. Then through terminal, go to that directory, and run this command:

$ docker build -t cowsay .

Once the image is built, run the following command:

$ docker run cowsay

The following screenshot shows the output of the preceding command:

The ENTRYPOINT instruction

If you look at the screenshot closely, the first run has no arguments and it used the argument we configured in the Dockerfile. However, when we gave our own arguments in the second run, it overrode the default and passed all the arguments (The -f flag and the sentence) to the cowsay folder.

Note

If you are the kind who likes to troll others, here's a tip: apply the instructions given at http://superuser.com/a/175802 to set up a pre-exec script (a function that is called whenever a command is executed) that passes every command to this Docker container, and place it in the .bashrc file. Now cowsay will print every command that it execute in a text balloon, being said by an ASCII cow!

The WORKDIR instruction

The WORKDIR instruction sets the working directory for the RUN, CMD, and ENTRYPOINT Dockerfile commands that follow it:

WORKDIR /path/to/working/directory

This instruction can be used multiple times in the same Dockerfile. If a relative path is provided, the WORKDIR instruction will be relative to the path of the previous WORKDIR instruction.

The EXPOSE instruction

The EXPOSE instruction informs Docker that a certain port is to be exposed when a container is started:

EXPOSE port1 port2 …

Even after exposing ports, while starting a container, you still need to provide port mapping using the -p flag to Docker run. This instruction is useful when linking containers, which we will see in Chapter 3, Linking Containers.

The ENV instruction

The ENV command is used to set environment variables:

ENV <key> <value>

This sets the <key> environment variable to <value>. This value will be passed to all future RUN instructions. This is equivalent to prefixing the command with <key>=<value>.

The environment variables set using the ENV command will persist. This means that when a container is run from the resulting image, the environment variable will be available to the running process as well. The docker inspect command shows the values that have been assigned during the creation of the image. However, these can be overridden using the $ docker run –env <key>=<value> command.

The USER instruction

The USER instruction sets the username or UID to use when running the image and any following the RUN directives:

USER xyz

The VOLUME instruction

The VOLUME instruction will create a mount point with the given name and mark it as holding externally mounted volumes from the host or from other containers:

VOLUME [path]

Here is an example of the VOLUME instruction:

VOLUME ["/data"]

Here is another example of this instruction:

VOLUME /var/log

Both formats are acceptable.

The ADD instruction

The ADD instruction is used to copy files into the image:

ADD <src> <dest>

The ADD instruction will copy files from <src> into the path at <dest>.

The <src> path must be the path to a file or directory relative to the source directory being built (also called the context of the build) or a remote file URL.

The <dest> path is the absolute path to which the source will be copied inside the destination container.

Note

If you build by passing a Dockerfile through the stdin file (docker build - < somefile), there is no build context, so the Dockerfile can only contain a URL-based ADD statement. You can also pass a compressed archive through the stdin file (docker build - < archive.tar.gz). Docker will look for a Dockerfile at the root of the archive and the rest of the archive will get used as the context of the build.

The ADD instruction obeys the following rules:

  • The <src> path must be inside the context of the build. You cannot use ADD ../file as .. syntax, as it is beyond the context.
  • If <src> is a URL and the <dest> path doesn't end with a trailing slash (it's a file), then the file at the URL is copied to the <dest> path.
  • If <src> is a URL and the <dest> path ends with a trailing slash (it's a directory), then the content at the URL is fetched and a filename is inferred from the URL and saved into the <dest>/filename path. So, the URL cannot have a simple path such as example.com in this case.
  • If <src> is a directory, the entire directory is copied, along with the filesystem metadata.
  • If <src> is a local tar archive, then it is extracted into the <dest> path. The result at <dest> is union of:
    • Whatever existed at the path <dest>.
    • Contents of the extracted tar archive, with conflicts in favor of the path <src>, on a file-by-file basis.
  • If <dest> path doesn't exist, it is created along with all the missing directories along its path.

The COPY instruction

The COPY instruction copies a file into the image:

COPY <src> <dest>

The Copy instruction is similar to the ADD instruction. The difference is that the COPY instruction does not allow any file out of the context. So, if you are streaming Dockerfile via the stdin file or a URL (which doesn't point to a source code repository), the COPY instruction cannot be used.

The ONBUILD instruction

The ONBUILD instruction adds to the image a trigger that will be executed when the image is used as a base image for another build:

ONBUILD [INSTRUCTION]

This is useful when the source application involves generators that need to compile before they can be used. Any build instruction apart from the FROM, MAINTAINER, and ONBUILD instructions can be registered.

Here's how this instruction works:

  1. During a build, if the ONBUILD instruction is encountered, it registers a trigger and adds it to the metadata of the image. The current build is not otherwise affected in any way.
  2. A list of all such triggers is added to the image manifest as a key named OnBuild at the end of the build (which can be seen through the Docker inspect command).
  3. When this image is later used as a base image for a new build, as part of processing the FROM instruction, the OnBuild key triggers are read and executed in the order they were registered. If any of them fails, the FROM instruction aborts, causing the build to fail. Otherwise, the FROM instruction completes and the build continues as usual.
  4. Triggers are cleared from the final image after being executed. In other words they are not inherited by grand-child builds.

Let's bring cowsay back! Here's a Dockerfile with the ONBUILD instruction:

FROM ubuntu:14.04
RUN apt-get -y install cowsay
RUN apt-get -y install fortune
ENTRYPOINT ["/usr/games/cowsay"]
CMD ["Docker is so awesomoooooooo!"]
ONBUILD RUN /usr/games/fortune | /usr/games/cowsay

Now save this file in a folder named OnBuild, open a terminal in that folder, and run this command:

$ Docker build -t shrikrishna/onbuild .

We need to write another Dockerfile that builds on this image. Let's write one:

FROM shrikrishna/onbuild
RUN  apt-get moo
CMD ['/usr/bin/apt-get', 'moo']

Note

The apt-get moo command is an example of Easter eggs typically found in many open source tools, added just for the sake of fun!

Building this image will now execute the ONBUILD instruction we gave earlier:

$ docker build -t shrikrishna/apt-moo apt-moo/
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon
Step 0 : FROM shrikrishna/onbuild
# Executing 1 build triggers
Step onbuild-0 : RUN /usr/games/fortune | /usr/games/cowsay
 ---> Running in 887592730f3d
 ________________________________
/ It was all so different before 
 everything changed.            /
 --------------------------------
           ^__^
           (oo)\_______
            (__)       )/
                ||----w |
                ||     ||
 ---> df01e4ca1dc7
 ---> df01e4ca1dc7
Removing intermediate container 887592730f3d
Step 1 : RUN  apt-get moo
 ---> Running in fc596cb91c2a
                 (__)
                 (oo)
           /------/
          / |    ||
         *  /---/
            ~~   ~~
..."Have you mooed today?"...
 ---> 623cd16a51a7
Removing intermediate container fc596cb91c2a
Step 2 : CMD ['/usr/bin/apt-get', 'moo']
 ---> Running in 22aa0b415af4
 ---> 7e03264fbb76
Removing intermediate container 22aa0b415af4
Successfully built 7e03264fbb76

Now let's use our newly gained knowledge to write a Dockerfile for the code.it application that we previously built by manually satisfying dependencies in a container and committing. The Dockerfile would look something like this:

# Version 1.0
FROM dockerfile/nodejs
MAINTAINER Shrikrishna Holla <s**[email protected]>

WORKDIR /home
RUN     git clone  https://github.com/shrikrishnaholla/code.it.git

WORKDIR code.it
RUN     git submodule update --init --recursive
RUN     npm install

EXPOSE  8000

WORKDIR /home
CMD     ["/usr/bin/node", "/home/code.it/app.js"]

Create a folder named code.it and save this content as a file named Dockerfile.

Note

It is good practice to create a separate folder for every Dockerfile even if there is no context needed. This allows you to separate concerns between different projects. You might notice as you go that many Dockerfile authors club RUN instructions (for example, check out the Dockerfiles in dockerfile.github.io). The reason is that AUFS limits the number of possible layers to 42. For more information, check out this issue at https://github.com/docker/docker/issues/1171.

You can go back to the section on Docker build to see how to build an image out of this Dockerfile.

The ONBUILD instruction
The ONBUILD instruction
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset