Speed Up Bazel in GitHub Actions
Last updated
To speed up Bazel in GitHub Actions, keep Bazel's caches alive between runs. Use bazel-contrib/setup-bazel to cache Bazelisk, the repository cache and a disk cache, and move to a remote cache when your repo is large. Bazel is built around cached, reproducible actions, but a fresh CI VM throws all of that away unless you save it.
This guide shows the options in order of effort. For the general picture, see how to speed up GitHub Actions.
What a cold Bazel build costs
On a clean machine Bazel has to do three expensive things: download itself (through Bazelisk), fetch external repositories (rules, toolchains, third-party code), and execute every action. Each has its own cache, and each needs saving separately. The first two are easy. The third is where the large gains and the real decisions are.
setup-bazel does the common part
bazel-contrib/setup-bazel is the community-maintained action. Its README documents these inputs, all off by default:
bazelisk-cache: caches Bazelisk downloads, keyed on.bazelversion.repository-cache: caches Bazel's repository cache, keyed onMODULE.bazelandWORKSPACEfiles or files you name.disk-cache: enables Bazel's disk cache, stored in GitHub's cache. Acceptstrueor a string to separate caches, for example one per workflow.external-cache: cachesexternal/repositories larger than 10 MB.bazelrc: extra lines written to the user bazelrc, as multiline YAML.cache-save: whether to save at the end. The README suggestsfalsefor pull requests.
- uses: actions/checkout@v7
- uses: bazel-contrib/setup-bazel@0.19.1
with:
bazelisk-cache: true
disk-cache: ${{ github.workflow }}
repository-cache: true
cache-save: ${{ github.ref_name == 'main' }}
bazelrc: |
build --config=ci
- run: bazel test //...
The version in the README example was 0.19.1 when we checked in October 2026; check its releases page for the current tag, since this action is versioned by exact tag rather than a moving major.
With the disk cache on, Bazel stores action outputs by content hash. If an action's inputs have not changed since the last saved cache, Bazel copies the output instead of running it. The GitHub cache has size limits, so see GitHub Actions cache limits. A big monorepo's disk cache can exceed what is useful to keep there, which leads to the next option.
A remote cache for big repos
A remote cache is a server that stores action results for everyone: CI, laptops and PRs. Bazel talks to it over HTTP or gRPC:
# .bazelrc
build:ci --remote_cache=grpcs://cache.example.com
build:ci --remote_upload_local_results=false
build:ci --remote_timeout=60
Common practice is to let only trusted CI jobs on main upload (--remote_upload_local_results=true) and have PRs read only. That keeps poisoned or half-built results out. Options include self-hosted servers such as bazel-remote, bring-your-own storage buckets, and commercial services. Store the credentials as secrets and pass them through --remote_header or credential helpers. We do not recommend a specific vendor here; compare on price, region and read speed from your CI machines.
Remote execution goes further: actions run on a pool of workers. It pays off for very large builds, and needs real infrastructure. Start with caching.
Flags that matter in CI
build:ci --jobs=auto
build:ci --keep_going
build:ci --show_timestamps
build:ci --test_output=errors
build:ci --disk_cache=~/.cache/bazel-disk
--jobssets how many actions run at once. The default is based on CPU count. If your actions are CPU-bound,autois right. If they are I/O or network bound (remote cache), a higher number can help.--keep_goingfinishes as many targets as possible and reports all failures in one run, which saves rerun cycles.--test_output=errorskeeps logs short.--local_resourcescaps memory; Bazel can use a lot on a 7 GB runner. If the job is killed with exit code 137 it ran out of memory, see exit code 137.
Run less with target selection
Bazel's dependency graph tells you exactly what a change affects. Tools such as bazel-diff compute impacted targets between two commits, so a PR runs bazel test on only what changed. This often beats any caching tweak for large monorepos. For other monorepo tools, see monorepo CI with Nx or Turborepo.
Sharding tests
Bazel shards a single test target with shard_count in the BUILD rule, and runs test targets in parallel by default. If one target is the bottleneck, increase shard_count and make sure the test framework supports Bazel's sharding protocol. Across machines, split the target list in a matrix. See test sharding in GitHub Actions.
What more CPU cores buy you
Bazel is one of the best users of extra cores because it runs many independent actions at once. With a warm cache most actions are skipped, so cores matter on the part that changed. A cold CI run on a 2-vCPU runner is limited by CPU. We have no Bazel row in our benchmarks, so we do not quote a number. The sensible test is to run the same bazel test //... with a warm cache on two machine sizes and compare cost per run with the cost calculator.
compiler.dev runs jobs on 8 vCPU / 16 GB machines by default, with 2, 4, 16 and 32 vCPU also available; the change is one line, runs-on: [self-hosted, Linux, compiler-ci]. See pricing.
FAQ
Do I need Bazelisk in GitHub Actions?
setup-bazel installs Bazelisk, which reads .bazelversion and downloads the right Bazel. Cache it with bazelisk-cache: true.
Disk cache or remote cache?
Start with the disk cache stored via setup-bazel. Move to a remote cache when the repo is large, the GitHub cache fills up, or you want developers to share results with CI.
Why is my Bazel cache never hit in CI?
Usually because inputs differ between runs: absolute paths, timestamps, environment variables or non-hermetic toolchains. Check with --explain and --verbose_explanations.
Should pull requests write to the cache?
The setup-bazel README suggests cache-save: false for PRs. Let main write and PRs read.
Related
- Add a CI Speed Badge to Your READMEShow how fast your CI is with a README badge: the median GitHub Actions run time for your repository, updated automatically. Free for public repos.
- Fastest CI for Bazel Monorepos on GitHub ActionsFast Bazel CI on GitHub Actions. Target determination for affected builds, remote cache flags, read-only PR uploads, sharding and a complete workflow.
- Speed Up .NET in GitHub ActionsSpeed up .NET builds in GitHub Actions: NuGet caching with setup-dotnet, --no-restore, --no-build, parallel dotnet test, and SQL Server services.
- Speed Up Django in GitHub ActionsSpeed up Django tests in GitHub Actions with pip or uv caching, pytest-xdist, --reuse-db, a Postgres service, and test splitting. Working YAML.