GitHub Actions — Go CI
A minimal, fast GitHub Actions CI workflow for Go projects: build every package, run the full test suite with verbose output, and vet for common mistakes — all in under 30 seconds on a fresh runner.
Overview
Go's toolchain is self-contained and fast, which makes Go CI workflows pleasantly simple compared to
ecosystems that require heavy package manager setup. This workflow uses the official
actions/setup-go action to install a specific Go version, then runs three standard
Go toolchain commands: go build, go test, and go vet.
Unlike the Node.js and Python examples, this workflow doesn't use a version matrix — Go projects
typically target a single Go version at a time (specified in the project's go.mod file).
If you need to test backward compatibility, the matrix pattern from the
Node.js CI example applies equally
to Go: just add go-version: ['1.21', '1.22', '1.23'] under strategy.matrix.
.github/workflows/ci.yml. Your repository must have a valid
go.mod file at the root (or in the directory you're building from) for the Go commands
to work correctly.
Full YAML Copy-paste ready
name: Go CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
- name: Vet
run: go vet ./...
Key sections explained
actions/setup-go@v5 with a pinned version
The go-version: '1.22' setting installs Go 1.22.x (the latest patch release within that
minor version). This is quoted as a string for the same reason Python versions are quoted: without quotes,
YAML would interpret 1.22 as a floating-point number, which could cause unexpected behavior
or version resolution failures depending on the action version.
actions/setup-go@v5 automatically caches the Go module download cache and the Go build cache
by default when a go.sum file is present — no extra cache configuration is needed, unlike
older versions of the action.
go build -v ./...
The ./... pattern is a Go convention meaning "this package and all packages in subdirectories."
It's the idiomatic way to target an entire module. The -v flag enables verbose output,
printing the name of each package as it's compiled. This makes it easier to diagnose build failures
— you can see exactly which package triggered the error.
Running go build separately from go test catches compilation errors quickly,
before the (potentially longer) test step runs. In large modules, this can save meaningful CI time.
go test -v ./...
go test -v runs all tests with verbose output — each test function name and its pass/fail
status are printed as the suite runs, rather than only printing failures. This is generally preferred in
CI environments where you want a complete record of what ran in the build log.
Tests that include data race conditions are the most common category of intermittent Go CI failures.
Add the -race flag (go test -v -race ./...) to enable Go's built-in race
detector. It adds some overhead but catches a class of bugs that are very difficult to debug in production.
go vet ./...
go vet is a static analysis tool included in the Go toolchain. It catches a range of common
mistakes that the compiler doesn't flag: incorrect format string verbs (e.g. %d for a string),
unreachable code, suspicious composite literals, and more. It's not a linter — it's specifically designed
to catch correctness bugs, and it has very few false positives.
go vet is free and always available — there's no reason not to run it in every CI pipeline.
It catches bugs that code review and tests often miss.
Tips & variations
Add golangci-lint for extended static analysis
go vet covers the basics, but golangci-lint runs dozens of additional linters
(including staticcheck, errcheck, and gosec) in one fast pass.
The official action makes it easy to add:
- name: Lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.59
Generate a coverage report
Add the -coverprofile flag to produce a coverage file, then upload it:
- name: Test with coverage
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: coverage.out
Use go-version-file to read from go.mod
Instead of hardcoding the version in the YAML, you can tell setup-go to read it from your
go.mod file:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
This keeps your CI version in sync with the version declared in your module — no need to update the workflow file when you upgrade Go.