Building the app, installing dependencies and services, automating deployment, and more—it all starts with Dockerfile. Let’s review the syntax, from basic to elaborate, and some best practices when creating your Docker images.
In this guide, we will write a Dockerfile instructing Docker to select a minimum Linux (base image) for the application we will deliver, and send with it a set of tools of our choice and a certain configuration, effectively building our own Linux distribution that is suitable for running our application.
<img src="https://uploads.sitepoint.com/wp-content/uploads/2016/11/1479211726build-image-with-dockerfile.jpg" alt="
Creating an image with Dockerfile
With Docker you can “Build, ship, and run any application, anywhere.” That is, you can package your application with all the runtime binaries and libraries, back-end tools, operating system tweaks, and even specific services your application needs to run, and make it available for instant delivery and automatic deployment.
The software container technology that Docker implements is what makes it possible. And while I won’t cover many of the details behind it here, you can read more about Docker, what software containers are, and how they work in Understanding Docker, Containers, and Safer Software Delivery.
Installing Docker Before you
begin, you’ll need to have Docker installed, either on your local machine or on a remote server
Luckily, the latest version of Docker (1.12 at the time of writing) made the installation process really smooth, and it has easy-to-follow guides for Windows, MacOS, and Linux.
The Dockerfile To create an image in Docker, you must first set the instructions for this build to a plain text file called
and a context (more on this later). This file has a syntax similar to that of Apache configuration files: one statement per line with its respective arguments, and all instructions processed in sequence, one after the other. Comments are preceded by the # character and a blank space. Finally, once you have a Dockerfile, the docker build command will compile the image, as we’ll see in more detail later.
Before we start writing the Dockerfile, we’ll set the workspace. We will create a directory called my_image in our home directory, use it as our working directory and place
the Dockerfile there: mkdir ~/my_build cd ~/my_build touch Dockerfile
Now we are ready to start building
Selecting the base image
of the time, when creating an image, you will use a starting point, i.e. Another image. This can be an official Ubuntu, MySQL, WordPress or any other image available on Docker Hub. You can also use an image that you created yourself before.
Note: You can create your own base image with your own basic tools and directory structure, using the minimum reserved Docker image, called scratch. It’s a process I won’t cover here, but you can check out the Docker site’s guide on how to create a base image.
For example, if you want to start with a minimal Debian distribution, you will add the following content to the
Dockerfile: # set the base image FROM debian
FROM should be the first statement you use when writing a Dockerfile. Note that you can also use a specific version of the base image, adding : and the version_name to the end of the image name. For example
: # set the base image FROM debian:sid In the
code above, we are using the Debian “sid” (unstable distribution). This will be relevant also when you want a specific version of a Ruby or Python interpreter, a version of MySQL, or whatever, when you use an official base image for any of these tools. For now, we’ll stick to the default (stable) Debian image for this guide.
Specifying a maintainer and adding
metadata Optionally, you can specify who the MAINTAINER is, replacing Lucero del Alba
with his name or the person or team responsible for the compilation: # author MAINTAINER Lucero del Alba
It is not necessary, but we can also add some metadata using the LABEL statement, and this information will be available later when we use the docker inspect command to examine the image:
# additional metadata LABEL version=”1.0″ LABEL description=”First image with Dockerfile”.
For more information about this feature, see Docker object tags.
Making your own distro
At this point, we are going to select some tools and libraries to include in our image, so that our container has everything it needs for what we intend it to do. At the end of this tutorial, we will do something that is very close to building a Linux distribution.
Some containers, such as one running a PostgreSQL database, are designed to run in the background. But we often need a console to perform some operations on the container, so we will likely need some additional tools, because the base image will include only a minimal set of GNU tools.
It is almost guaranteed that you will experience cache issues when trying to install additional packages on your image. This is because the base image comes with cached metadata, and the active repositories from which it pulls data often change.
In Debian-based distributions, you can handle this by adding the following commands before installing new packages
: # update source list RUN apt-get clean RUN apt-get update
Code editors, locales, tools like Git or TMUX: this is the time to install everything you’ll need later, so they’re included in the image.
We will install one per line:
# install basic apps, one per line for better caching RUN apt-get install -qy git RUN apt-get install -qy local RUN apt-get install -qy nano RUN apt-get install -qy tmux RUN apt-get install -qy wget
We could install them all on a single line, but if we then want to add or remove a package, we must rerun the whole process. Therefore, the best practice here is to install one package per line so that you can benefit from Docker caching.
Also, keep it tight. You don’t want to install tools “just in case” as this can increase compile time and image size.
Installing runtime libraries for your
We will also submit our app in this image. Do you need a specific version of PHP, Ruby or Python, along with certain modules? Now is the time to deliver all the programs and runtimes that our application will need.
Be as specific as you want, as this container is designed to run only your application:
# install runtimes and application modules RUN apt-get install -qy python3 RUN apt-get install -qy python3-psycopg2 RUN apt-get install -qy python3-pystache RUN apt-get install -qy python3-yaml
For this example, we will install Python 3 with the Psycopg 2 packages (to connect to PostgreSQL databases), the Mustache module for Python and the YAML module. (Naturally, you’ll install the specific dependencies you need by making your own Dockerfile.)
Building and downloading packages
It is also possible that your distribution does not have a package for a certain module or program that you need. But you don’t need to manually install it in your running container! Instead, you can use the RUN statement (one per line) to batch process the download, build, and configure library process that your application needs.
You can even write a script in a separate file, add this file to the build, and run it, as we’ll see later in the “Submitting Your Own Application” section.
To keep your image tidy and as small as possible, it’s also a good idea to do a cleanup at the end of the installation sequence: # cleanup
RUN apt-get -qy autoremove
Again, note that we are using apt-get because we chose Debian, but use the appropriate command for your base image distribution.
Submitting your own app
The goal of creating this environment is so that you can deliver your application smoothly and ready to run. To add files, directories, and even the contents of remote URLs to the image, we’ll use the ADD statement.
However, before adding files, we must put them in the appropriate context. To make things easier, we’ll simply locate everything in the my_build directory mentioned above, along with the Dockerfile itself.
Let’s say that, with the app and everything we want to put in the image, we
have the following files in ~/my_build (where app.py and lib.py are inside
the app/ subdirectory): .bashrc .profile app/app.py app/lib.py Dockerfile
We will add .bashrc and .profile scripts to the /root directory in the container to run every time we launch a shell in the container, And we will copy the contents of app/ in the /app/ directory of the container.
We added the following instructions:
# add scripts to the container ADD .bashrc /root/.bashrc ADD .profile /root/.profile # Add the app to the ADD container app /app
will set some environment variables that we will need at the system and application level.
Many of you will do well with the default Debian character set, but since we are targeting an international audience, let’s see how to have a UTF-8 terminal. We previously installed the locale package, so all we have to do now is generate the character sets and set the appropriate Linux environment: # locales
for UTF-8 RUN locale-gen C.UTF-8 && /usr/sbin/update-locale LANG=C.UTF-8 ENV LC_ALL C.UTF-8
You may also need to set some environment variables for your application, to exchange passwords and routes.
The Dockerfile provides the ENV statement to do just this: # ENV PYTHONIO application environmentUTF-8 ENV PYTHONPATH coding /app/
Note that you can also pass environment variables from the command line when launching the container, which can be convenient for sharing sensitive information such as passwords.
The complete Dockerfile Naturally, you will have to adapt the Dockerfile
to your needs, but I hope you get an idea of the possibilities
Here is the full file:
# author MAINTAINER Lucero del Alba # additional metadata LABEL version=”1.0″ LABEL description=”First image with
.” # set the base image FROM debian # update RUN font list apt-get clean RUN apt-get update # install basic applications, one per line for better caching RUN apt-get install -qy git RUN apt-get install -qy local RUN apt-get install -qy nano RUN apt-get install -qy tmux RUN apt-get install -qy wget # install runtime applications and modules RUN apt-get install -qy python3 RUN apt-get install -qy python3-psycopg2 RUN apt-get install -qy python3-pystache RUN apt-get install -qy python3-yaml # cleanup RUN apt-get -qy autoremove # add scripts to the container ADD .bashrc /root/. bashrc ADD .profile /root/.profile # add the application to the container ADD app /app # locals to UTF-8 RUN locale-gen C.UTF-8 && /usr/sbin/update-locale LANG=C.UTF-8 ENV LC_ALL C.UTF-8 # app environment ENV PYTHONIOENCODING UTF-8 ENV PYTHONPATH /app/
From within the my_build directory, we will use the docker build command, Passing the -t flag to “tag” the new image with a name, which in this case will be my_image. The. indicates that the Dockerfile is in the current directory, along with the so-called “context”, i.e. the rest of the files that may be in that location:
cd ~/my_build docker build -t my_image .
That will generate a long output where each “step” is an instruction in our Dockerfile. This is truncated output
: Send build context to Docker 5.12 kB daemon Step 1 : FROM debian -> 7b0a06c805e8 Step 2 : MAINTAINER Dawn Lucifer -> Execution on d37e46e5455d -> 2d76561de558 Removing the intermediate container d37e46e5455d Step 3: LABEL version “1.0” -> Running on 904dde1b4cd7 -> a74b7a492aaa Removing the intermediate container 904dde1b4cd7 Step 4 : Description of LABEL “First image with Dockerfile”. -> Execution in 9aaef0353256 -> 027d8c10e966 Intermediate deletion container 9aaef0353256 Step 5 : RUN apt-get clean -> Run on bc9ed85dda16 -> a7407036e74a Removing the intermediate container bc9ed85dda16 Step 6 : RUN apt-get update -> Run on 265e757a7563 Get:1 http://security.debian.org jessie/updates InRelease [63.1 kB] ign http://deb.debian.org jessie InRelease Get:2 http://deb.debian.org jessie-updates InRelease [145 kB] Get:3 http://deb.debian.org jessie Release.gpg [2373 B] Get:4 http://deb.debian.org jessie Release [148 kB] Get:5 http://security.debian.org Jessie/Updates/Main amd64 packages [402 kB] Get:6 http://deb.debian.org Jessie-Updates/Main amd64 packages [17.6 kB] Get 7 http://deb.debian.org Jessie/Main AMD64 packages [9064 kB] Obtained 9843 kB in 10s (944 kB/s) Reading package lists… -> 93fa0a42fcdc Removal from intermediate container 265e757a7563 Step 7 : RUN apt-get install -qy git -> Running on c9b93cecd953 (…)
We can list
our images with the command docker images: docker images
This will generate our newly created my_image along with other base images we have downloaded:
REPOSITORY TAG IMAGE ID CREATED SIZE my_image last e71dc183df2b 8 seconds ago 305.6 MB debian latest 7b0a06c805e8 2 weeks ago 123 MB debian sid c1857cb435d7 3 weeks ago 97.77 MB
… And there it is, our image is ready to ship and run!
a container Finally, to
launch an interactive terminal of our newly created image, we will use the docker run command: docker run
-ti my_image /bin/bash
What to do next
I haven’t covered all the possibilities of Dockerfile. In particular, I haven’t reviewed how to EXPOSE ports so you can run services and even link containers together; how HEALTHCHECK containers to verify that they still work; or even how to specify a VOLUME to store and retrieve data from the host machine… among other useful features.
We will be able to cover them in future articles. For now, you may want to check out the following resources.
the Docker website
: Dockerfile reference
- Best practices for writing Dockerfiles
- Containers, and Delivering More Secure Software
- All Docker related articles