Skip to content
compiler.dev

Cache Go Modules and Build Cache in GitHub Actions

Last updated

To cache Go in GitHub Actions, use actions/setup-go. It caches Go modules and build outputs by default, keyed on go.sum. There are two caches worth having: the module cache (GOMODCACHE, downloaded dependencies) and the build cache (GOCACHE, compiled packages and test results). The built-in cache covers both.

Option 1: setup-go

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v7
        with:
          go-version-file: go.mod
          cache-dependency-path: go.sum
      - run: go build ./...
      - run: go test ./...

Per the setup-go README (checked October 2026), cache defaults to true, and the action caches Go modules and build outputs. The default dependency file is go.mod at the repo root; set cache-dependency-path to go.sum to key on the checksums file, or to a glob when you have several modules:

- uses: actions/setup-go@v7
  with:
    go-version-file: go.mod
    cache-dependency-path: |
      go.sum
      tools/go.sum

Turn it off with cache: false, which you might want on workflows that publish releases.

Option 2: explicit actions/cache

Ask Go where its caches are, so the paths are right on every OS:

- uses: actions/setup-go@v7
  with:
    go-version-file: go.mod
    cache: false
- name: Find Go cache paths
  id: go-cache
  run: |
    echo "mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT"
    echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v6
  with:
    path: |
      ${{ steps.go-cache.outputs.mod }}
      ${{ steps.go-cache.outputs.build }}
    key: go-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/go.sum') }}
    restore-keys: |
      go-${{ runner.os }}-${{ runner.arch }}-
- run: go test ./...

On Linux these are ~/go/pkg/mod and ~/.cache/go-build. Setting cache: false avoids two overlapping caches.

Two caches, two jobs

The module cache is keyed well on go.sum: it contains exactly what the checksums list. The build cache is different. Go's build cache is content-addressed: an entry is valid only if the source, compiler flags and dependencies hash the same. So a restored build cache from yesterday's commit is mostly still valid, and Go simply rebuilds what changed.

That means a prefix restore-keys fallback is especially useful for GOCACHE. A go.sum change doesn't invalidate most compiled packages; a stale entry is harmless because Go checks content hashes.

CacheContentsSavesKey on
GOMODCACHEDownloaded module sourcesNetwork timego.sum
GOCACHECompiled packages, test resultsCompile and test timego.sum + fallback prefix

Test results in GOCACHE are real: go test reports (cached) for packages whose inputs did not change. In CI that can skip whole packages, but only with the same flags. Note that -race builds use separate cache entries from plain builds, and -count=1 disables test result caching. If you need fresh results every run, expect that to cost time.

Speed-ups beyond the cache

  • go mod download as a separate step makes dependency time visible in logs.
  • go test -race is slower by design (often 2 to 10 times). In compiler.dev's measured run of React plus Go plus Postgres with go test -race, CI went from 6m13s to 2m42s mostly through more CPU (see benchmarks).
  • Split packages across jobs with test sharding; Go's go list ./... makes a natural split.
  • -p controls build parallelism; the default is GOMAXPROCS, which tracks core count.
  • For huge repos, consider a remote cache (GOCACHEPROG is supported in recent Go versions) rather than pushing everything through the 10 GB GitHub cache.

How to verify a cache hit

  1. The setup-go step logs Cache restored from key: setup-go-Linux-... on a hit, and Cache not found on a miss. Its post step logs Cache saved.
  2. Run go build -x locally or in a debug job: restored packages are not recompiled. go test prints (cached) for unchanged packages.
  3. go mod download should finish in well under a second when GOMODCACHE is warm.
  4. gh cache list --key setup-go --limit 20 shows size and last-used.

Common mistakes

  1. Hashing go.mod when the module graph is in go.sum. Both change together in most repos, but go.sum is the checksum record; key on it for explicit caches.
  2. A vendored repo with a cache. With go mod vendor committed there is no module download to cache. Cache only GOCACHE.
  3. Caching GOPATH/bin. Tools installed with go install should be keyed on tool version.
  4. Several Go versions in one key. The build cache is partitioned by Go version internally, but mixing them in one entry wastes space. Put the version in the key for matrices.
  5. Saving only on PRs. Populate from main. See Caching in GitHub Actions.

FAQ

Does setup-go cache by default?

Yes. The cache input defaults to true. Set cache: false to turn it off.

Should I cache GOCACHE or just modules?

Both. Module downloads are quick on a good network; compilation is where the time goes, and GOCACHE is what saves it.

Why does go test still run every time?

Test caching is skipped with -count=1, with changed files or environment variables the test reads, and for tests that open files outside the module. Check for -count=1 in your command.

How big does the Go cache get?

Usually a few hundred MB, larger with CGo or many test binaries. See cache limits.

Next steps

See also speeding up Go CI end to end. Cargo has a similar split: caching Rust builds. For an end-to-end checklist, see how to speed up GitHub Actions. compiler.dev runs the same job on 8 vCPU for $0.012 per minute; see pricing.