Speed up Bazel CI with remote caching — and verify it actually works
Reuse Bazel build outputs across machines with remote caching, but only after you confirm cache hits and hermetic builds.
13 Nov 2025, 16:19 UTC

The problem: CI rebuilds what it already built
In a monorepo or distributed team, CI jobs often compile the same unchanged libraries over and over. Each job starts from an empty workspace, so even a one-line change can trigger recompilation of dependencies that were already built on another machine hours ago. Bazel remote caching lets a job download a previously built output instead of rerunning the action, skipping compilation and test execution when the action hash matches.
What remote caching actually does
Bazel keys every build action by a hash of its inputs, command line, and configuration. When you enable a remote cache, Bazel sends that hash to the cache server. If the server already has an output for the same hash, Bazel downloads it and marks the action as a cache hit. If not, Bazel runs the action locally and can upload the result for future jobs.
Two flags control this behavior:
--remote_cache— the URL of the cache server, used for both reads and writes unless separate endpoints are configured.--remote_upload_local_results— tells Bazel to upload locally built outputs to the cache.
Cache hits depend on hermetic builds: actions must not depend on undeclared environment variables, absolute paths, or local timestamps. If those leak into the action key, identical source code can produce different hashes, or worse, the cache can return an output built in a different environment.
Worked example: enabling remote caching in CI
Add the cache URL to your project’s .bazelrc so developers and CI share the same endpoint. Replace https://cache.example.com with your actual cache server.
build --remote_cache=https://cache.example.com
build --remote_upload_local_results=true
Run from a CI runner or developer terminal with network access to the cache server and read/write permissions on the cache bucket or service:
bazel build //my_app:binary --remote_cache=https://cache.example.com --remote_upload_local_results=true
Expected check: on the first run, actions execute and upload. On the second run with no source changes, Bazel output should include lines such as Cache hit for unchanged targets, and build time should drop because compilation is skipped.
Risk: enabling uploads sends build outputs to the cache server. If the server is shared or external, internal artifacts may be exposed. Use TLS and access controls; cache contents are not encrypted by Bazel automatically.
Verify before you trust cache hits
Do not assume a cache hit means the output is correct. Run this diagnostic locally before enabling uploads in CI:
- Run
bazel build //my_app:binaryonce without remote caching and note the output. - Run the same command again with
--remote_cacheenabled but uploads disabled. - Confirm unchanged actions report cache hits and the resulting binary behaves identically.
- Compare
bazel build --profile=out.jsonwith and without the cache to measure execution time saved.
If a target that should change shows a cache hit, your action inputs are likely missing a dependency—an environment variable or generated file that Bazel does not see. Fix the BUILD file or toolchain before relying on the cache.
Trade-offs and limitations
| Factor | Impact |
|---|---|
| Clean builds | Little to no benefit; every action must execute at least once to populate the cache. |
| Network and storage | Uploads add traffic and storage cost, especially for large binaries or test logs. |
| Security | Outputs travel over the network; use TLS and restrict cache access. Do not cache secrets. |
| Stale results | If action keys miss an input, Bazel may reuse an outdated output after a dependency changes. |
Incremental builds benefit most. A heavily changed dependency graph still requires local execution for the affected actions.
Rollback
Remote caching is a configuration change, not a data migration. To disable it, remove --remote_cache and --remote_upload_local_results from .bazelrc or your CI command. Existing cache entries remain on the server but are no longer used.
Actionable closing
Start with read-only caching in CI to measure hit rates without uploading artifacts. Once hits are stable and builds are hermetic, enable uploads on trusted CI runners. Verify with a double build and a profile comparison before making remote caching a default for your team.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.