Purpose

You want to know:

  • how to Dockerize your Go app
  • how to configure your Docker build image to pull from a private GitHub repository
  • how multi-stage docker build works
  • how to make your Docker image as small as possible
  • how to add metadata to your Docker image
    • to know where is the source code for the docker build
    • to know which branch/git commit it is using
    • to know when was the last build

Creating and using a GitHub OAuth token

Personal access tokens (PATs) are an alternative to using passwords for authentication to GitHub when using the GitHub API or the command line.

https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token

Select the scopes, or permissions, you’d like to grant this token.

To use your token to access repositories from the command line, select repo.

In your Dockerfile during the build stage you will add these commands:

ARG GITHUB_TOKEN

RUN git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/<USERNAME>/".insteadOf "https://github.com/<USERNAME>/"

The GITHUB_TOKEN environment variable is injected to Docker image dynamically on CI server’s agent.

  • on local machine a developer can use her/his personal access token

Using git config --global url.<base>.insteadOf trick allow us to inject the token as a part of requests made to GitHub by go mod to fetch private repository.

ARG makes the variable available only during building an image.

Allowing go mod to download from private Git repositories

The go mod command defaults to

  • downloading modules from the public Go module mirror at goproxy.io
  • validate downloaded modules, regardless of source, against the public Go checksum database at sum.golang.org

These defaults work well for publicly available source code.

The GOPRIVATE environment variable controls which modules the go command considers to be private (not available publicly) and should therefore not use the proxy or checksum database.

The variable is a comma-separated list of glob patterns (in the syntax of Go’s path.Match) of module path prefixes. For example:

GOPRIVATE=*.corp.example.com,rsc.io/private,github.com/your-org-name/*

You can set the value for the GOPRIVATE variable using the go env command.

For example, if you want to allow all private repos from your organization:

$ go env -w GOPRIVATE=github.com/<OrgNameHere>/*

Adding Label Schema

There is a standardised naming conventions for labels in Docker image, which can be found here.

http://label-schema.org/rc1/

In our Dockerfile, we specify the ARG to be passed during the build process.

The full ultimate Dockerfile

# Builder image
# This is the intermediate layer where the Go build tools and compilation happen.
# -------------------------------------------------------------------------------
FROM golang:1.16-alpine as build
LABEL stage=builder

# GitHub token for downloading private dependencies
ARG GITHUB_TOKEN

WORKDIR /src

COPY . .

# Replace <OrgNameHere> with your (or your Company) Github handle.
RUN apk add --no-cache git ca-certificates && \
    git config --global url."https://$GITHUB_TOKEN:x-oauth-basic@github.com/<OrgNameHere>/".insteadOf "https://github.com/<OrgNameHere>/" && \
    go env -w GO111MODULE=on && \
    go env -w GOPRIVATE=github.com/<OrgNameHere>/* && \
    go mod tidy && \
    CGO_ENABLED=0 go build -ldflags '-w -s' -o /bin/app

# Deployment environment
# ----------------------
FROM scratch

COPY --from=build /bin/app /bin/app

# Metadata params
ARG VERSION
ARG BUILD_DATE
ARG VCS_URL
ARG VCS_REF
ARG NAME
ARG VENDOR
ARG DESCRIPTION

# Metadata
LABEL org.label-schema.build-date=$BUILD_DATE \
      org.label-schema.name=$NAME \
      org.label-schema.description=$DESCRIPTION \
      org.label-schema.vcs-url=https://github.com/$VCS_URL \
      org.label-schema.vcs-ref=$VCS_REF \
      org.label-schema.vendor=$VENDOR \
      org.label-schema.version=$VERSION \
      org.label-schema.docker.schema-version="1.0"

CMD ["/bin/app"]

…and the ultimate Makefile

We can create a simple Makefile to ease storing all the variables for the Dockerfile metadata and for the docker build command:

#!make

# This trick allows us to include and export a `.env` file content
include .env
export $(shell sed 's/=.*//' .env)

# Name part of the Docker image tag
NAME := $(shell basename "$(PWD)")
# Git commit hash
VERSION := $(shell git rev-parse HEAD)
# Build date
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
# Repo URL
VCS_URL := $(shell git config --get remote.origin.url | sed "s/git@github.com\://; s/\.git//")
VCS_REF := $(shell git log -1 --pretty=%h)
# Version part of the Docker image tag
TAG := $(shell git describe --tags --abbrev=0 | sed -n "s/^v\(.*\)$$/\1/p")
# The vendor part of the Docker image tag
VENDOR := $(shell echo $(VCS_URL) | sed "s/\/.*//")

print:
	@echo VERSION=${VERSION} 
	@echo BUILD_DATE=${BUILD_DATE}
	@echo VCS_URL=${VCS_URL}
	@echo VCS_REF=${VCS_REF}
	@echo NAME=${NAME}
	@echo VENDOR=${VENDOR}
	@echo TAG=${TAG}

build:
	docker build -t "${VENDOR}/${NAME}:${TAG}" \
	--build-arg GITHUB_TOKEN="${GITHUB_TOKEN}" \
	--build-arg DESCRIPTION="${DESCRIPTION}" \
	--build-arg VERSION="${VERSION}" \
	--build-arg BUILD_DATE="${BUILD_DATE}" \
	--build-arg VCS_URL="${VCS_URL}" \
	--build-arg VCS_REF="${VCS_REF}" \
	--build-arg NAME="${NAME}" \
	--build-arg VENDOR="${VENDOR}" .

clean:
	docker container prune --force
	docker rmi $(shell docker images -f "dangling=true" -q --no-trunc)

Running:

$ make build

will create your Docker image file.