Using Bazel Remote Caching to Speed Up CI Builds
Learn how to enable Bazel remote caching, avoid common pitfalls, and verify that your CI builds are actually reusing cached outputs.
01 Sept 2025, 22:25 UTC

The problem: redundant work in CI pipelines
When many developers push changes to the same repository, CI systems often rebuild the same libraries and binaries over and over. Each rebuild consumes CPU time, increases queue latency, and raises infrastructure costs. The goal is to avoid re‑executing actions whose inputs have not changed.
How Bazel remote caching works
Bazel treats every build step as an action (e.g., compile a .c file, link a binary, run a test). Before executing an action, Bazel computes a hash from the action’s inputs, command line, and environment variables. If that hash matches a key already stored in a remote cache, Bazel downloads the cached outputs instead of running the action locally. The action remains hermetic because the hash is verified first; the cache is merely a shortcut for the output.
Enabling remote caching
To turn on remote caching you must tell Bazel where the cache lives and ensure all participants use identical toolchains.
- Choose a cache service – Bazel supports HTTP, gRPC, or a shared filesystem. For a simple start, an HTTP endpoint backed by a storage bucket (e.g., Google Cloud Storage, S3) works well.
- Configure the flag – Add the following line to your workspace’s
.bazelrc(or pass it on the command line):
Replacebuild --remote_cache= --noremote_upload_local_resultswith the endpoint, e.g.,https://storage.googleapis.com/my-bazel-cache. The--noremote_upload_local_resultsflag prevents Bazel from uploading results from the local machine, which is useful when you want a read‑only cache for CI. - Toolchain consistency – All machines that read from or write to the cache must use the same compiler version, flags, and environment variables. Mismatched toolchains produce different hashes, causing cache misses.
- Permissions – The CI runner needs read access to the cache; if you enable uploading, it also needs write access. Use the least‑privilege credentials appropriate for your storage service.
Worked example: speeding up a Java CI build
Consider a multi‑module Java project with a CI pipeline that runs bazel build //... on every pull request. The first build takes about 12 minutes; subsequent builds on the same commit should be near‑instant if the outputs are cached.
- On a developer workstation, warm the cache by running a full build once:
This uploads all action outputs to the cache.bazel build --remote_cache=https://storage.googleapis.com/my-bazel-cache //... - In the CI configuration (e.g., GitHub Actions), add the same remote cache flag but disable uploads to avoid unnecessary writes:
bazel build --remote_cache=https://storage.googleapis.com/my-bazel-cache --noremote_upload_local_results //... - When the CI job runs, Bazel will compute hashes for each action. If the inputs match those from the warm‑up build, you will see log lines like:
Action cache hit: //src/main/java/com/example:MainClass (reused)indicating that the action’s outputs were downloaded instead of recompiled.
After enabling the cache, measure the CI build time again. A noticeable drop (e.g., from 12 minutes to 3 minutes) shows the cache is being used.
Trade‑offs and limitations
- Network latency – If the cache is far away or the connection is slow, downloading outputs can take longer than recompiling locally, especially for small actions. Test with a typical action size before committing to a remote cache.
- Cache integrity – A corrupted cache entry (e.g., from a faulty toolchain or a sandbox escape) will propagate incorrect builds. Periodically verify checksums of cached objects or use a cache service that offers immutable storage.
- Storage growth** – Bazel does not automatically evict old entries. You must implement a cleanup policy (e.g., lifecycle rules on the storage bucket) to prevent unbounded costs.
- Toolchain drift** – Upgrading a compiler or changing flags changes the hash, causing cache misses until the new toolchain is used everywhere. Coordinate toolchain upgrades across all cache participants.
Practical verification steps
- Log inspection – After a build, search the Bazel output for
Action cache hit. The presence of these lines confirms cache usage. - Build‑time comparison** – Run the same build twice on a clean workspace: once with
--remote_cacheand once without. Compare the elapsed times; a significant reduction indicates the cache is effective. - Cache inspection** – If you control the cache storage, list a few objects and verify that their keys match the hash Bazel computes (you can reproduce the hash locally with
bazel query --output=json //...and compare). This step ensures the cache is being populated correctly.
These checks give you confidence that remote caching is working without relying on unverified claims.
Actionable closing
Remote caching is a low‑effort way to cut repetitive work in Bazel‑based CI pipelines, provided you maintain a stable toolchain and monitor network performance. Start by configuring a read‑only HTTP cache in your .bazelrc, warm it with a local build, and then enable the flag in your CI with uploads disabled. Verify with cache‑hit logs and build‑time measurements, then put a lifecycle rule in place to control storage costs. Once those pieces are in place, you’ll see faster feedback loops and lower compute bills.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.