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!