Inject Build-time vars with Golang

In this blog post I’m going to show you how to inject variables into your Golang executable at build-time. This is most useful for tagging your binary with a version or Git shasum or digest from version control.

You may also like my new ebook – Everyday Golang which is full of practical examples and tips from open-source Go applications including JSON, HTTP servers, embedding, databases, templates and Goroutines.

Here’s an example where the Docker project’s CLI contains a commit ID of 77b4dce:

$ docker version
Client:
 Version:      17.06.1-ce-rc1
 API version:  1.30
 Go version:   go1.8.3
 Git commit:   77b4dce
 Built:        Fri Jul 14 07:38:15 2017
 OS/Arch:      darwin/amd64

Why add versioning information?

This is a great way to support your users – you can quickly and easily identify which build they are using and how old it could be.

Capture useful variable(s)

Before you get started, think about what makes sense to be injected at build-time? It could be anything from the hostname of the CI machine to the result of a web-service call or more commonly the last commit ID from your Git log.

Let’s use the last Git commit id.

Create a test Git project with a README

$ mkdir /tmp/git-tester && \\
 cd /tmp/git-tester && \\
 git init && \\
 echo "Let's work with Git" > README && \\
 git add . && \\
 git commit -m "Initial" && \\
 echo "Let's keep working" >> README && \\
 git add . && \\
 git commit -m "First update"

You’ll now see two commits in your Git log:

$ git log
commit 67b05a31758848e1e5237ad5ae1dc11c22d4e71e
Author: Alex Ellis <alexellis2@gmail.com>
Date:   Tue Aug 8 08:37:20 2017 +0100

    First update

commit 9b906b6d02d803111250a974ed8042c4760cde7f
Author: Alex Ellis <alexellis2@gmail.com>
Date:   Tue Aug 8 08:37:20 2017 +0100

    Initial

Here’s how you find the ID of your last commit:

$ git rev-list -1 HEAD
67b05a31758848e1e5237ad5ae1dc11c22d4e71e

Next we can capture that into an environmental variable:

$ export GIT_COMMIT=$(git rev-list -1 HEAD) && \\
  echo $GIT_COMMIT

67b05a31758848e1e5237ad5ae1dc11c22d4e71e

Prepare your code

Let’s take hello world:

package main

import (
        "fmt"
)

funcmain() {
        fmt.Println("Hello world")
}

In order to pass a build-time variable we need to create a variable within our main package. We’ll call it var GitCommmit string.

package main

import (
        "fmt"
)

var GitCommitstringfuncmain() {
        fmt.Printf("Hello world, version: %s\\n", GitCommit)
}

Test it out with go build:

$ go build && \\
  ./git-tester
Hello world, version:

The version is empty, but we’re now ready to start injecting a variable.

Override go build

Now we need an additional override to our go build command to pass information to the linker via the -ldflags flag.

$ export GIT_COMMIT=$(git rev-list -1 HEAD) && \\
  go build -ldflags "-X main.GitCommit=$GIT_COMMIT"

Now we see our application has a hard-coded version which we injected at build-time.

$ ./git-tester
Hello world, version: 67b05a31758848e1e5237ad5ae1dc11c22d4e71e

Do it with Docker

Once you have worked out your build-time variables it’s likely you will want to update your Dockerfile.

Write a Dockerfile:

FROM golang:1.7.5

WORKDIR /go/src/github.com/alexellis/git-tester
COPY .git     .
COPY app.go   .

RUN GIT_COMMIT=$(git rev-list -1 HEAD) && \\
  go build -ldflags "-X main.GitCommit=$GIT_COMMIT"

CMD ["./git-tester"]

Dockerfile

Run a build and then test it out:

$ docker build -t git-tester .
$ docker run git-tester
Hello world, version: 67b05a31758848e1e5237ad5ae1dc11c22d4e71e

Now this is just a minimal example to show you how to get started with Docker and Go build-time variables. You can take it further and optimize the size of the Docker image by using Multi-stage builds.

Here’s an example of one of my Dockerfiles which builds a Golang application for a Raspberry Pi, MacOS and 64-bit Linux all in one file using multi-stage builds: