Base images for Docker to rule the world

Kenneth Belitzky
The Appraisal Lane Developers
3 min readNov 17, 2017

Looking at how we managed images across multiple projects at The Appraisal Lane, we saw there was room for improvement. The problem was that lots of our projects had the same repeated stuff across this definitions of the Dockerfile. That led to some disadvantages, mainly: maintenance, homogeneity and simplicity.

To solve this problem, the idea was to:

  • Take advantage of Dockerfile inheritance
  • Use of the layering concept
  • ONBUILD instruction for simplifying child images

Let’s get hands-on with the process we executed in order to get this solved.

The docker base image project

The first thing is to setup a project where all base images are defined, built and sent to the corresponding registry. For this, the proposed structure is as follows:

Structure of the docker base image project

First level directory will be the TYPE of image (base, nodejs, python, golang, etc). This level will group the different flavours we have inside (ubuntu, webapp, passenger, flask, etc).

The second level directory refers to the VERSION of the TYPE. As an example: 7.0 is the version of node being used in the image definition (5)

Third level directory we like to call it The FLAVOUR. Every type can have its ways of running and here we represent them using different flavours. for example to run a rails project you could use Puma or Passenger or any other while in Python you could have a Flask or Gunicorn to run the same project

Inside the flavour is the Dockerfile itself with the definitions needed for that image. The base webapp image has the ability to update all system packages, install base packages, and some specific stuff for our infrastructure so that the developers don’t need to handle anything when defining their Dockerfile for the project they create.

Dockerfile (5) inherits from the image generated by Dockerfile (3) like so:

FROM theappraisallane/base:0.1-webapp

and then defines specific stuff for that base image that will be used inside other nodejs projects.

All Dockerfile specific files are stored in docker-files folder (4) and are specifically added and referenced in that definition.

Last but not least: Convention over Configuration

Building images can be very easy with base images, and much more simple if you follow the conventions defined on your base images.

To take advantage and implement this conventions, we are using the ONBUILD instruction, which will execute a command on child images when they are build. As a great example of this is the following:

extract from the ruby:2.4-passenger Dockerfile definition

Having this convention defined with the development team: ruby projects will build executing the bundle install command and have a Gemfile definition for package management, made it possible to use the ONBUILD instruction to define the command on a base image level and not at a project level.

Conclusions

Following this rule set has made it more simple and convenient to define new projects that require a specific behavior. Teams understood the potential of working this way and the real management of docker images got centralized in one project and maintained by the DevOps team.

References

Best practices for writing Dockerfiles
https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/

Passenger Docs
https://www.phusionpassenger.com/library/

--

--