Stage 3 · Build
Go Fundamentals for SREs
CI/CD for Go Projects
GoReleaser, golangci-lint, and multi-arch releases for automated delivery.
CI Pipeline Structure
A solid Go CI pipeline runs linting, testing, building, and releasing. Each stage should be fast, deterministic, and produce clear failure messages.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- uses: golangci/golangci-lint-action@v4
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- run: go test -race -coverprofile=coverage.out ./...
- uses: codecov/codecov-action@v4Run lint and test in parallel for faster feedback. The -race flag enables the race detector. Coverprofile uploads coverage reports to track test coverage over time.
Linting with golangci-lint
golangci-lint runs dozens of linters in parallel. It catches bugs, style issues, and performance problems before they reach production.
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- bodyclose
- durationcheck
- exportloopref
- gosec
- nilerr
- prealloc
- unconvert
- unparam
linters-settings:
govet:
check-shadowing: true
issues:
exclude-rules:
- path: _test.go
linters:
- gosec
- unparamEnable linters that matter for infrastructure code. errcheck catches unchecked errors. gosec catches security issues. unparam catches unused function parameters. Exclude rules reduce noise in test files.
Testing in CI
Run tests with -race and -count for reliable results. Use -short to skip long-running tests in PR checks. Run full suites on main branch pushes.
# PR checks — fast feedback
go test -race -short -count=1 ./...
# Main branch — full suite
go test -race -count=5 -timeout=30m ./...
# Benchmarks — track performance
go test -bench=. -benchmem -count=3 -timeout=10m ./... | tee bench.txt
# Coverage
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out-count=5 runs tests multiple times to catch flaky tests. -timeout prevents hung tests from blocking CI. Save benchmark output to track performance regressions across commits.
GoReleaser
GoReleaser builds binaries, creates archives, generates checksums, and publishes releases. It handles cross-compilation, Docker images, and package manager distributions in one configuration.
project_name: kube-guard
builds:
- binary: kube-guard
goos:
- linux
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
archives:
- format: tar.gz
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
checksum:
name_template: "checksums.txt"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"GoReleaser reads git tags to determine the version. The -s -w flags strip debug information for smaller binaries. ldflags inject version and commit at build time. Changelog auto-generates from commit messages.
Multi-Architecture Builds
Infrastructure tools often run on multiple architectures — amd64 for servers, arm64 for Graviton and Apple Silicon. GoReleaser handles this natively.
dockers:
- image_templates:
- "ghcr.io/myorg/kube-guard:{{ .Version }}-amd64"
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.version={{.Version}}"
- image_templates:
- "ghcr.io/myorg/kube-guard:{{ .Version }}-arm64"
use: buildx
build_flag_templates:
- "--platform=linux/arm64"
- "--label=org.opencontainers.image.version={{.Version}}"
docker_manifests:
- name_template: "ghcr.io/myorg/kube-guard:{{ .Version }}"
image_templates:
- "ghcr.io/myorg/kube-guard:{{ .Version }}-amd64"
- "ghcr.io/myorg/kube-guard:{{ .Version }}-arm64"Build separate images per architecture, then combine them into a multi-arch manifest. Docker pulls the correct image automatically based on the host architecture.
Docker Images in CI
Build minimal Docker images in CI and push them to registries. Use multi-stage builds to keep images small and secure.
FROM golang:1.22 AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
FROM gcr.io/distroless/static:nonroot
COPY --from=build /app/server /server
USER 65534:65534
ENTRYPOINT ["/server"]Distroless images contain only your binary and minimal runtime dependencies. No shell, no package manager, no attack surface. The nonroot user runs the process as a non-privileged user.
distroless images are maintained by Google and contain only the minimal runtime needed for Go binaries. They are significantly smaller and more secure than alpine or debian-based images.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.