Test Driven Development for your Dockerfiles

Jesse Adametz
Sep 8, 2017 · 4 min read

Chances are if you’re reading this article, you’re just like me…

You strive for “best practices.” You relentlessly seek high quality software. Or, at the very least, you (or more likely your company) practice Test Driven Development (TDD).

Usually TDD would be discussed in reference to an application you were building in some language. But as my team and I embark on rewriting all of the Dockerfiles for my company (Invoca — we’re hiring!) to make them “production ready,” we found ourselves needing to solve two problems:

  1. A way to continue practicing TDD
  2. A framework (ideally a container) for testing our other container images

After all, why should Dockerfiles be any different than the software our Devs write or the configuration management we on the Ops team write?

Over the next few minutes my goal is to walk you through jadametz/docker-serverspec. A GitHub repository which not only builds a Serverspec Docker image (our container for testing other containers), but serves as a working example for how to write integration tests for your Dockerfiles!

Oh, and hopefully you came here for code samples. Lots of code samples.

Let’s first set some common vocabulary and understanding.

A Dockerfile is a text document that contains all of the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession. [1]

Test Driven Development is a software development process that relies on the repetition of a very short development cycle: Requirements are turned into very specific test cases, then the software is improved to pass the new tests, only. [2]

Serverspec is essentially RSpec tests for your servers configuration. Serverspec tests the actual state of your server (image).

Let’s TDD the creation of jadametz/serverspec

So in usual TDD fashion we’ll start by creating our first (failing) test. We’ll create spec/Dockerfile_spec.rb with the following content:

Alright so we’ve already done a lot, but let me explain! We’re using some third party gems and have started to set the context for our tests. before we execute any tests though, we need to setup.

Docker::Image.build_from_dir('.') is going to build our Dockerfile into an image and subsequently tag it jadametz/serverspec:latest.

After that, Serverspec needs to know some information about how to run the tests. What OS it’s running against, that the backend for the tests will be Docker, and as a result which image to use.

:docker_container_create_options is kind of special in this case and admittedly took a little while to figure out. Because our image is going to have an ENTRYPOINT, we need to override it during testing so that it isn’t executed when Serverspec runs a container from our image.

Our first test!

With setup out of the way, if we run the test (L15–17) we’ll see that it fails since we don’t even have a Dockerfile yet.

To complete this cycle we need to write code that satisfies the test. We’ll use the ruby:2.4.1-alpine image and add a maintainer label.

There are two “flavors” of tests

Before we continue I should point out that we’re going to be using two different styles of tests.

Our test for the maintainer label is what we’ll call “config” testing. These tests are using pure rspec to parse the JSON output of docker inspect on our image.

A very heavily truncated version of `docker inspect jadametz/serverspec`

The other type of test is what we’ve been eluding to all along, Serverspec. Remember, these tests are used to verify the actual state of the image. There’s a ton you can do but for now, we’ll just be verifying the installation of some packages and ensuring some files exist.

Installing an apt package

Getting used to TDD yet? We’ve added the test and it’s failing. Lucky for us, this is a one-line cycle. Simply add the build-base package to the image and we’re set!

Installing gems from our Gemfile

We need to COPY our Gemfile and Gemfile.lock, then use bundle install to get all the necessary gems. Great, moving on!

And last but not least…

At the end of the process the Dockerfile needs (should have) instructions on how to run. We’re going to have our ENTRYPOINT be responsible for executing rspec. The user will then give the path to their spec(s). If they don’t, we’ll default the CMD to printing the help menu with -h.

And with the last two Dockerfile instructions added, we’re “all tests pass!”

Phew! Thanks for sticking with me through the wall-o-gists. Hopefully by now you have a better understanding on how to carry your TDD practices over to Docker. Are you stoked to try this on some of your images?

Half the work is already done!

Because what we built here is a Serverspec image, you can start using it immediately to test your images. You don’t need Serverspec, you don’t need the docker-api Gem (although you will need to require them in your spec). You just need your own Dockerfile_spec.rb file.

I’d love to hear what you think!

I genuinely hope this is useful and that you can find use for this Docker image.

⭐️ the repository: https://github.com/jadametz/docker-serverspec

👋🏼 on Twitter: @jadametz

👏🏼 to show your support for this article: ⬇️

Jesse Adametz

Written by

Manger, Site Reliability Engineering @Invoca.