Configuring the build cache for Bazel in local builds
You can use the Bitrise Build Cache for local Bazel builds too. Your local builds then read from the same cache as your CI builds, so an action your CI already ran doesn't have to run again on your machine.
The Bitrise Build Cache CLI sets this up for you by writing the cache flags to your ~/.bazelrc.
Before you startClick to copy link
Ensure you have:
- A working Bazel project on your machine, on Bazel 6 or later. The setup uses Bazel's credential helper support, which earlier versions don't have.
- A Bitrise workspace with the Build Cache enabled. Check it on the Build Cache page.
activate bazel writes a single block to ~/.bazelrc in your home directory. It never touches your project's .bazelrc.
Every Bazel project on your machine picks up the cache flags, not just the one you activated from. The repository URL reported to the dashboard is read from the git remote of the directory you ran the activation in, so on a machine with several Bazel projects they all report the first one. That affects dashboard attribution only, not cache correctness. Re-run the activation from another project's directory to change it.
Installing the CLIClick to copy link
Install the CLI with Homebrew (recommended):
brew install bitrise-io/bitrise-build-cache/bitrise-build-cache
Or, without Homebrew:
curl --retry 5 -sSfL \
'https://raw.githubusercontent.com/bitrise-io/bitrise-build-cache-cli/main/install/installer.sh' \
| sh -s -- -b ~/.local/bin
Make sure the install location is on your PATH, then check the install:
bitrise-build-cache --version
Keep the CLI on your PATH afterwards. Bazel runs the CLI on every build to fetch a fresh auth token, not only during setup.
Activating the cacheClick to copy link
Run the interactive wizard:
bitrise-build-cache activate --interactive
The wizard asks for the following:
- Sign in to Bitrise: opens your browser for authentication on the first run. The CLI stores the credentials in the OS keychain and refreshes them automatically, so later runs skip this step. On a machine without a usable keychain, the CLI falls back to storing them in a config file.
- Select a workspace: pick the workspace whose Build Cache you want to use. The CLI selects it automatically if you only have access to one.
- Which build tools should I set up: ensure Bazel is selected. Use space to toggle an option and enter to confirm.
- Display name for this machine's local invocations: the name your local builds show up under in the Build Cache dashboard, for example
local-<yourhandle>. - Enable cache push: select No, pull only. See Local builds only read from the cache.
The non-interactive equivalent, once you have signed in:
bitrise-build-cache activate bazel --cache
What the activation writesClick to copy link
A single marked block appended to ~/.bazelrc. Existing content is preserved, and re-running the activation only updates the block:
# [start] generated-by-bitrise-build-cache
build --credential_helper=*.services.bitrise.io=bitrise-build-cache
build --remote_cache=grpcs://bitrise-accelerate.services.bitrise.io
build --remote_timeout=600s
build --remote_header=x-flare-buildtool=bazel
build --remote_header=x-flare-builduser=
build --noremote_upload_local_results
build --bes_backend=grpcs://flare-bes.services.bitrise.io:443
build --bes_results_url=https://app.bitrise.io/build-cache/invocations/bazel/
build --bes_timeout=2m
build --bes_upload_mode=wait_for_upload_complete
build --build_event_publish_all_actions
build --show_timestamps
build --remote_header='x-org-id=<workspace-id>'
build --bes_header='x-org-id=<workspace-id>'
build --remote_header='x-repository-url=https://github.com/<org>/<repo>.git'
build --bes_header='x-repository-url=https://github.com/<org>/<repo>.git'
build --bes_header='x-os=<detailed OS info>'
build --bes_header='x-locale=C'
build --bes_header='x-default-charset=UTF-8'
build --bes_header='x-cpu-cores=4'
build --bes_header='x-mem-size=16759259136'
# [end] generated-by-bitrise-build-cache
Two lines worth highlighting:
--credential_helperpoints at the CLI rather than storing a token, so Bazel fetches a fresh one on every build and an expiring login keeps working. This is why the CLI has to stay on yourPATH.--noremote_upload_local_resultsis pull-only mode. Activating with--cache-pushwrites--remote_upload_local_resultsinstead.
The x-os, x-cpu-cores, x-mem-size, x-locale, and x-default-charset headers describe your machine and are used for analytics only, and don't affect cache keys. They matter if you commit the configuration, because they're specific to the machine that generated the block. See What is safe to commit.
Verifying the setupClick to copy link
Run the CLI's health check:
bitrise-build-cache doctor
It reports the status of every part of the local setup and ends with an overall verdict. The probes for the other build tools report as skipped:
Bitrise Build Cache - doctor
CLI version: 3.x.y
Healthy:
✓ auth OAuth login (keychain) (workspace <id>), token valid until <iso-timestamp>
✓ keychain-smoke Set/Get/Delete round-trip OK
✓ auth-backend latency <ms>, source=keychain, workspace=<id>
✓ xcelerate-proxy skipped (xcode not activated)
✓ xcelerate-wrapper-path skipped (xcode not activated)
✓ xcelerate-enrichment skipped (xcode not activated)
✓ ccache-helper skipped (c++ not activated)
✓ ccache-binary skipped (c++ not activated)
✓ log-dirs no activated tool writes logs
Overall: ok
To let the CLI repair the issues it can fix on its own, run bitrise-build-cache doctor --fix --interactive.
Running a buildClick to copy link
-
Run
bazel cleanfirst. Bazel's local action cache sits in front of the remote cache, so if a target is already built locally, Bazel serves it from disk and never contacts Bitrise - and you see no evidence that the remote cache works.cd path/to/your/bazel/projectbazel clean -
Build your target:
bazel build //your/target:name
A build that hits a warm cache ends like this:
(16:47:35) INFO: Invocation ID: <invocation-id>
(16:47:35) INFO: Streaming build results to: https://app.bitrise.io/build-cache/invocations/bazel/<invocation-id>
(16:47:36) INFO: Analyzed target //src/close-matching-prs:close-matching-prs (91 packages loaded, 9216 targets configured).
(16:47:36) INFO: Found 1 target...
(16:47:36) INFO: Elapsed time: 0.934s, Critical Path: 0.18s
(16:47:36) INFO: 11 processes: 4 remote cache hit, 7 internal.
(16:47:36) INFO: Build completed successfully, 11 total actions
Checking that it workedClick to copy link
The summary line is the proof. It reads differently in each of the three cases:
| Summary line | What it means |
|---|---|
11 processes: 4 remote cache hit, 7 internal. | Working. The actions were fetched from the Bitrise Build Cache. |
1 process: 4 action cache hit, 1 internal. | Served from your local cache. The remote cache was never contacted, so run bazel clean first. |
11 processes: 7 internal, 4 processwrapper-sandbox. | Built locally from scratch. Either the cache has no entries for this target, or authentication failed. Look for WARNING: Remote Cache: UNAUTHENTICATED earlier in the output. |
internal actions, such as symlinks and file writes, are never cacheable. The ratio that matters is remote cache hits against the actions that aren't internal.
| Signal | Where to find it | What success looks like |
|---|---|---|
INFO: N processes: X remote cache hit | Bazel build summary | A non-zero remote cache hit count after a bazel clean |
INFO: Streaming build results to: | First and last lines of the build | A link to the invocation is printed |
| Dashboard | The printed link, or the Build Cache page | A row appears under your display name, with the same hit counts |
Local builds only read from the cacheClick to copy link
The setup in this guide activates the Build Cache in pull-only mode: your local builds read from the shared cache but never write to it. This is the recommended mode for local development.
Build tools recommend writing cache entries only from an environment where the source files don't change during the build. On a local machine you might keep editing files while a build is running, which can produce cache entries that don't match their inputs, and those entries would then be served to your teammates and to CI. Pull-only removes that risk: a broken local build can't affect anyone else.
The usual pattern is to have CI populate the cache, because CI builds from a clean, fixed checkout, and to let local machines pull from it.
Pushing to the cache from local buildsClick to copy link
Pull-only assumes that something else fills the cache, which is normally CI. If nothing does, your local builds have nothing to read: the summary line keeps reporting 0 remote cache hit.
If your team doesn't run the Build Cache on CI, turn pushing on for your local builds and leave it on. Your machine then populates the cache as you work, for you and for your teammates.
Re-run bitrise-build-cache activate --interactive and answer Yes, push too at the cache push prompt when you run the wizard, or run the non-interactive activate command:
bitrise-build-cache activate bazel --cache --cache-push
You can check which mode you're in without running a build:
grep upload_local_results ~/.bazelrc
# --noremote_upload_local_results → pull-only
# --remote_upload_local_results → push enabled
An entry written from a build whose source files changed while it was running can be wrong, and your teammates read the same entry. Avoid editing files during a build you push from.
Setting up the Build Cache on CI is the more robust option, because CI builds from a clean, fixed checkout. Once it runs there, switch your machine back to pull-only.
Committing the configuration to your repositoryClick to copy link
Bazel already reads a .bazelrc from your workspace root, so the cache flags can be committed to the repository instead of being generated on every machine. Onboarding a dev machine then comes down to installing the CLI and signing in:
brew install bitrise-io/bitrise-build-cache/bitrise-build-cache
bitrise-build-cache auth login
How the files combineClick to copy link
Bazel reads its configuration files in this order, and the last value wins for a single-valued flag:
- The system file,
/etc/bazel.bazelrc - The workspace file,
<repo>/.bazelrc(the committed one) - The home file,
~/.bazelrc(what the activation writes, if it was ever run) - Command-line flags
So the home file takes precedence over the committed one, and command-line flags beat both. Add --announce_rc to a build to see what Bazel actually loaded.
Commit pull-only and let CI opt into pushClick to copy link
A developer who only installs the CLI and runs auth login never gets a ~/.bazelrc, so there is nothing to override the committed block. Whatever you commit is what they build with, so commit the safe mode:
build --noremote_upload_local_results
On CI, run the activation with pushing enabled before the build. Because the home file beats the committed workspace file, it overrides the committed pull-only flag on that machine only:
bitrise-build-cache activate bazel --cache --cache-push
That gives you the split with no per-developer setup: the committed file is the pull-only default everyone inherits, and the one environment that should write to the cache is the one that runs the activation.
What is safe to commitClick to copy link
| Lines | Commit | Why |
|---|---|---|
--remote_cache, --remote_timeout, --bes_backend, --bes_results_url, --bes_timeout, --bes_upload_mode, --build_event_publish_all_actions, --show_timestamps | Yes | Identical on every machine |
--remote_header='x-org-id=…' and the matching --bes_header | Yes | Workspace-wide, not machine-specific |
--remote_header='x-repository-url=…' and the matching --bes_header | Yes | It identifies this repository |
--noremote_upload_local_results | Yes, in the pull-only form | Every developer who only runs auth login inherits it. CI flips it by running the activation with --cache-push |
--credential_helper=*.services.bitrise.io=bitrise-build-cache | Yes, after checking the value | It's the bare binary name, which Bazel looks up on PATH. If the activation ran on a machine where the CLI wasn't on PATH, the CLI writes an absolute path instead. Don't commit that form |
--bes_header='x-os=…', x-cpu-cores, x-mem-size, x-locale, x-default-charset | No | Per-machine, and x-os contains your hostname. They're analytics only and don't affect cache keys, but committing them attributes everyone's builds to your machine |
--remote_header=authorization="Bearer …" | Never | A live credential. It only appears in a block generated on CI |
What happens without the CLI installedClick to copy link
If you commit the --credential_helper line, a teammate who hasn't installed the CLI can't build at all. Bazel looks the helper up on PATH, doesn't find it, and fails while initializing the remote cache:
ERROR: Could not find file with name 'bitrise-build-cache' on PATH '...'
ERROR: Could not find file with name 'bitrise-build-cache' on PATH '...'
ERROR: Could not find file with name 'bitrise-build-cache' on PATH '...'
ERROR: Error initializing RemoteModule
Bazel exits with code 2 and no targets are built. There's no fallback to a local build, because the failure happens before the build starts.
Installing the CLI and running bitrise-build-cache auth login on that machine resolves it.
Remote Build ExecutionClick to copy link
If Remote Build Execution is enabled for your workspace, you can use it locally by adding the --rbe flag to the activation. You need the workers set up for your workspace and the pool configuration in your repository's .bazelrc first. See Remote Build Execution for Bazel.
TroubleshootingClick to copy link
Start with the CLI's health check. It inspects every part of the local setup and repairs the issues it can fix on its own:
bitrise-build-cache doctor --fix --interactive
If you're still experiencing issues, check the following table:
| Issue | Fix |
|---|---|
| The wizard reports that it needs a terminal | Run TERM=dumb bitrise-build-cache activate --interactive for line-based mode. |
The build reports action cache hit instead of remote cache hit | Run bazel clean so Bazel has to fetch from the remote cache. |
The build fails with Could not find file with name 'bitrise-build-cache' on PATH | The CLI isn't installed, or isn't on your PATH. Install it, then run bitrise-build-cache auth login. |
| The build fails while fetching credentials, naming the credential helper | The CLI is installed but has no credentials. Run bitrise-build-cache auth login. |
doctor reports a problem it can't fix | Re-run it with --debug for the full context. |