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.

🛠 Paste this YAML into the validator to check it instantly.

Open Validator →

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.

Save this file as .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

.github/workflows/ci.yml
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:

golangci-lint step
      - 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:

coverage steps
      - 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:

version from go.mod
      - 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.