We've been using docker on our projects recently to ease development and deployment processes. Here are a few tips based on what we learned building and maintaining docker infrastructure for production.

Docker image layers

In the early days of docker, there used to exist a pretty low number limit of layers: it was equal to the Ultimate Question of Life, the Universe, and Everything or if you prefer, the limit of aufs layers (42). That docker limitation has now been overcome, and the layer limit has been increased to 127.

Although the limitation has been increased, it is still a good practice to avoid creating excessive amounts of layers.

Writing Dockerfiles


Each instruction or step that is executed during a docker build run, creates a new layer and we want to minimize the amount of layers where it makes sense.

Using instructions with multiple operations

When using RUN try to concatenate several commands together making them make sense as a unit, that is, if you are installing Nginx, use the layer to install Nginx only and remove all the temporary files, to minimize space used by the layer. For example:

BAD:

RUN echo "\ndeb http://nginx.org/packages/debian/ jessie nginx" >> /etc/apt/sources.list
RUN wget http://nginx.org/packages/keys/nginx_signing.key -O - | apt-key add - && \
RUN apt-get update -y
RUN apt-get install -y --no-install-recommends nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
RUN rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin

GOOD:

RUN echo "\ndeb http://nginx.org/packages/debian/ wheezy nginx" >> /etc/apt/sources.list && \
  wget http://nginx.org/packages/keys/nginx_signing.key -O - | apt-key add - && \
  apt-get update -y && \
  DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nginx && \
  echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \
  rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin

Note that we remove all the temporary files before ending the command line for the RUN instruction because once a layer is committed, it keeps the content forever. If the content is removed in the next step/instruction the resulting image will not free up the space.

When using EXPOSE, you can specify as many ports as you want in a single line. For example:

BAD:

EXPOSE 2181
EXPOSE 2888
EXPOSE 3888

GOOD:

EXPOSE 2181 2888 3888

When using VOLUME, specify all the volumes in the same line as an array. For example:

BAD:

VOLUME /tmp/dir1
VOLUME /tmp/dir2

GOOD:

VOLUME ["/tmp/dir1", "/tmp/dir2"]

A couple of extra tips

When using ENV you can specify multiple environment variables in a single line resulting in a single layer. For example:

ENV TMPDIR="/tmp" APPDIR="/app"

The use of VOLUME or WORKDIR will result in the creation of a directory, not only as a mount point reference in the case of VOLUME, but also as a physical directory in the filesystem of the image. That is:

FROM busybox

VOLUME ["/newdir", "/app"]
WORKDIR /APP1

will create a docker image that has 3 new directories /newdir, /app and /APP1 of which two of them can be mounted with docker run -v ~/:/newdir imagename or docker run -v ~/:/app imagename and a third directory that is set as the CWD (current working directory) of the runtime.

We hope that you can take these practices and incorporate them into your own code to slim down your Dockerfiles. Got any other suggestions? Tweet them to us: @spantreellc