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 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
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:
RUN <command>
formRUN ["executable", "arg1", "arg2"...]
formIn 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:
docker build
command is run with the --no-cache
flag.apt-get update
is given. All the following RUN
instructions will be run again.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 provides the default command for a container to execute. It has the following forms:
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.
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:
Thus, you can watch Star Wars in your terminal!
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:
ENTRYPOINT ["executable", "arg1", "arg2"...]
form.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:
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:
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.
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 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 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 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 sets the username or UID to use when running the image and any following the RUN
directives:
USER xyz
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 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.
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:
<src>
path must be inside the context of the build. You cannot use ADD ../file as ..
syntax, as it is beyond the context.<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.<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.<src>
is a directory, the entire directory is copied, along with the filesystem metadata.<src>
is a local tar archive, then it is extracted into the <dest>
path. The result at <dest>
is union of:<dest>
.<src>
, on a file-by-file basis. <dest>
path doesn't exist, it is created along with all the missing directories along its path.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 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:
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.OnBuild
at the end of the build (which can be seen through the Docker inspect
command).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.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']
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
.
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.