About a month ago, Go version 1.11 was released. I’ve been looking forward to this release, because of the introduction of modules. These allow for a more efficient way of building Go applications with Docker than was possible before, without using non-official external dependency managers.
What’s new
In this new release, Go adds support for ‘modules’. This is an alternative to using GOPATH.
For binaries and libraries, you can now have a go.mod file describing the minimal version requirements on used dependencies. For binaries, you’d also want to include the go.sum file in version control. This last file will pin the exact version of the dependency that the Go compiler will use, making the build a lot easier to reproduce.
What benefits do these files have in practise? It allows your build system to be more intelligent about building Go projects!
Using modules to have faster Docker builds
The Go compiler is very fast. Anyone who has built a Go project with a few dependencies in Docker, knows the pain of waiting until all dependencies are downloaded, which makes the total build process a lot longer than if you ran it directly on your development machine. We can mitigate this by leveraging modules!
The snippet below is adapted from a Dockerfile that I wrote for a project that I’m working on. It uses a multi-stage docker file to re-use some of the layers of building the actual project for building e.g. a image to run the tests. I’ve added some extra inline comments to explain what is happening.
# Base build image
FROM golang:1.11-alpine AS build_base
# Install some dependencies needed to build the project
RUN apk add bash ca-certificates git gcc g++ libc-dev
WORKDIR /go/src/github.com/creativesoftwarefdn/weaviate
# Force the go compiler to use modules
ENV GO111MODULE=on
# We want to populate the module cache based on the go.{mod,sum} files.
COPY go.mod .
COPY go.sum .
#This is the ‘magic’ step that will download all the dependencies that are specified in
# the go.mod and go.sum file.
# Because of how the layer caching system works in Docker, the go mod download
# command will _ only_ be re-run when the go.mod or go.sum file change
# (or when we add another docker instruction this line)
RUN go mod download
# This image builds the weavaite server
FROM build_base AS server_builder
# Here we copy the rest of the source code
COPY . .
# And compile the project
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go install -a -tags netgo -ldflags '-w -extldflags "-static"' ./cmd/weaviate-server
#In this last stage, we start from a fresh Alpine image, to reduce the image size and not ship the Go compiler in our production artifacts.
FROM alpine AS weaviate
# We add the certificates to be able to verify remote weaviate instances
RUN apk add ca-certificates
# Finally we copy the statically compiled Go binary.
COPY --from=server_builder /go/bin/weaviate-server /bin/weaviate
ENTRYPOINT ["/bin/weaviate"]
If you’re running this in a CI system, you probably want to push the build_base stage to a registry after a successful build on your integration branch, and pull it before each build. Don’t forget to pull for an updated alpine after that too!
Conclusions
We have shown how to leverage Go 1.11’s new modules concept to make your Docker builds faster and more reliable.