8. Packaging

Basics

Go binaries are statically linked. That means all dependencies are included within the binary.

go build main.go

The binary will only work on the same platform that built it (e.g. 64bit Linux). Go allows us to easily generate binaries compatible with other architectures:

GOOS=linux GOARCH=arm64 go build main.go
GOOS=windows GOARCH=amd64 go build main.go

Docker

The Dockerfile uses multi-stage builds so that the resulting image is small and secure. The image is built with the full Golang Docker image and the resulting binary is copied into the Distroless image .

FROM golang:1.17 AS build
WORKDIR /src
# pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
# Build the executable
RUN CGO_ENABLED=0 go build -o /app ./main.go

FROM gcr.io/distroless/static
USER nonroot:nonroot
# Copy compiled app
COPY --from=build --chown=nonroot:nonroot /app /app
ENTRYPOINT ["/app"]

You might also see images online that are built with scratch . Scratch is a Docker image that contains absolutely nothing by default. We recommend using Distroless, because it already includes things like timezone data, user support and root certificates. The image is used by popular projects like Kubernetes .

Quality Control

Go Vet

go vet starts where the compiler ends by identifying subtle issues in your code. It’s good at catching things where your code is technically valid but probably not working as intended.

go vet main.go
# test all files
go vet ./...

Unreachable code is an example of something go vet would find. The program compiles and run without errors.

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
	fmt.Println("Before return")
	return
	fmt.Println("After return")
}
Output:
./main.go:8:2: unreachable code

Linting

There are a few community tools that go even further and add many more checks:

Testing

We already covered testing in chapter 4. Testing. Tests are important to ensure everything works.

Race conditions can occur when two pieces of code run concurrently. They are usually hard to debug and cause mysterious failures. To find race conditions Go offers a handy -race flag, which can be added to any Go command:

go test -race mypkg    # test the package
go run -race mysrc.go  # compile and run the program
go build -race mycmd   # build the command
go install -race mypkg # install the package

Versioning

Go packages follow Semantic Versioning .

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards compatible manner, and
  3. PATCH version when you make backwards compatible bug fixes.

Public Go packages should be backwards compatible .