The Go Dockerfile

Build With Dep, Ship From Scratch

For Go 1.11 modules, check out my latest post instead.

In a devops environment, pushing some code to the repository is not enough. You have to ship it. And the first step is often writing a Dockerfile.

The goals:

  1. The code has to be compiled in a container, to boost the chances my build will be reproducible.
  2. Use dep for fetching the dependencies in case the vendor folder is not committed alongside with the code. NOTE: if vendor/ is in the .gitignore, it should be in the .dockerignore too.
  3. The final image should be as small as possible. Go applications compile to a single binary. We can have images as small as our compiled binary by leveraging the special FROM scratch base image in a multi-stage build.

Here is my base Dockerfile for Go services:

This will work out of the box. But if you plan to use sub-packages, remember to replace “github.com/username/repo” on line 8 with the actual URI of your repository, in order to provide the compiler with a working import path.

One tricky part is on line 12: the go build step. What is that gibberish?

CGO_ENABLED=0 is an environment variable that tells to the compiler to disable the support for linking C code. As a result, the resulting binary will not be able to depend on the C system libraries. The point is that in a scratch container, there are no system libraries. If we omit this directive, the Docker build will terminate successfully, but the resulting container will crash with funny errors.

standard_init_linux.go:195: exec user process caused “no such file or directory”

installsuffix is probably not necessary in a Docker build, but it looks like it’s a pretty complicated matter. This option is meant to change the name of the folder used to stock the pkg files, by adding a custom suffix. If I get it, specifying a suffix is useful in order to force the compiler to rebuild everything and not rely on the contents of the current pkg folder. Basically, it has a similar meaning to the -a switch.

On line 14, FROM scratch tells Docker to start over, using a new empty base image. The first stage, FROM golang, will be discarded. The final Docker image will only contain this one, which doesn’t even carry an operating system.

COPY --from=builder is the switch for selecting one previous stage to copy a file from. At line 1, we have named the first stage with AS builder. We can now use that name to pick up /app and put it in this new empty stage.

Note that in the last line, the arguments to ENTRYPOINT are given as a JSON array: if instead a string had been passed, Docker would have invoked sh as a tokenizer. And… you have guessed it: there is no sh in a scratch container.

docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused “exec: \”/bin/sh\”: stat /bin/sh: no such file or directory”: unknown.

That’s it! What is your experience with Go Dockerfiles?

EDIT: dep ensure is now executed before COPYing the code from the repo, to allow for locally caching the dependency retrieval independently. Thank you, Reddit folks!