Scott Kidder Yet Another Technology-Themed Blog

Creating Minimal Docker Images for Go Apps with Wercker

For years I’ve been using CircleCI to do the typical CI things: building a project, running tests, packaging & uploading artifacts, and even deploying. Lately I’ve noticed a lot of reliability and performance issues with CircleCI and figured it was time to explore the CI landscape. I used Wercker one year ago to build some Bitbucket-hosted projects that were written in Java. Wercker performed wonderfully, was (and still is) free, and supports both Github and Bitbucket.

So, I decided to move my StreamMarker Golang projects to Wercker. The Wercker documentation includes some very simplistic examples of how to build Golang projects. These were a good starting point, but left out some vital details:

  • building projects that use Go 1.5+ experimental vendoring
  • building projects that include sub-packages

Go Experimental Vendoring

Glide is a Go dependency management tool that’s grown in popularity since late 2015. Glide supports Go 1.5 experimental vendoring. Projects using Glide include a glide.yml file enumerating their explicit dependencies. The Glide tool includes a glide update step that examines the glide.yml file and the project source to identify dependencies, and writes a glide.lock file describing exactly which dependencies and their version to use when building. Dependencies must be downloaded from their origin on each run of the CI build by running glide install. Hence, it can make CI a little tricky since the Glide tool must be installed.

I searched the Wercker Steps Registry for Glide build steps. There were two Step repositories, but they both reference ancient versions of Glide. I created my own fork that uses Glide 0.10.0 and published the step to the Wercker step repository. This makes the step available for use in the wercker.yml file included in each project I intend to build.

Building Projects with Sub-Packages

Wercker does not place the project source on the GOPATH by default. This complicates building in that you must manipulate the GOPATH environment variable or copy the project source to the appropriate location on the GOPATH. I went with the copy route.

My Resulting wercker.yml

Here are the contents of the wercker.yml file for my StreamMarker Collector component.

In order to build a Golang executable that can run in a minimal Docker container, your compilation step must produce a statically-linked binary. This is handled in the make static-build step in the wercker.yml file provided above. The static-build target in the Makefile looks like:

I configured Wercker to run the deploy step automatically, pushing this minimal container to Dockerhub for all successful builds. The resulting Docker image is extremely small, weighing in at just 2 MB! Minimal Docker images lead to faster installation & upgrades, have smaller disk & memory footprints, and are less exposed to security vulnerabilities. There’s a lot to like about minimal Docker images!