Local Remote Execution
The Nix-pinned toolchain workflow behind LRE, walked through and tested end to end — including exactly what runs fully local and what still needs a real worker.
Explanations → LRE covers what Local Remote Execution is and why it exists. This page is the hands-on version: the exact commands, run for real, plus two gotchas the flake alone doesn't mention.
Tested on macOS (aarch64-darwin) — read this before you start
@local-remote-execution ships two toolchain families, and they
behave differently once you leave x86_64-linux:
- Rust (
@local-remote-execution//rust/...) has a real, natively built toolchain foraarch64-darwin,aarch64-linux,x86_64-darwin, andx86_64-linux. Actions using it run fully local — no network, no container. - C++ (
@local-remote-execution//generated-cc/...) — what thebazeltemplate's own example uses — is a single Linux container config (ghcr.io/tracemachina/nativelink-worker-lre-cc:...). On any host, actions using it dispatch to a real worker running that container. On macOS this means a network round-trip even though the toolchain is Nix-pinned — LRE gives you hermeticity here, not offline-ness.
Below is what actually happened running the C++ example on aarch64-darwin. If your project is Rust, swap the target and you get the fully-local experience the intro promises.
1. Install Nix
Flakes enabled. The next-gen installer is the easiest path.
2. Pull the template
mkdir my-lre-test && cd my-lre-test
nix flake init -t github:TraceMachina/nativelink#bazelThis writes a flake.nix that imports nativelink.flakeModules.lre
and pins lre = { inherit (pkgs.lre.lre-cc.meta) Env; };, plus the
hello-world.cpp example, its BUILD.bazel, and platforms/BUILD.bazel.
3. Init git — this step is not optional
git init && git add -ASkip this and lre.bazelrc is silently never written
The flake module's install script checks for a .git directory
before generating anything. Running nix develop in a plain
(non-git) folder prints this and moves on without writing
lre.bazelrc — no error, no toolchain:
WARNING: lre: .git not found; skipping installation.4. Enter the dev shell
nix developThis fetches the Nix-pinned Clang and writes lre.bazelrc. On
aarch64-darwin, the generated file looked like this (paths and hashes
will differ on your machine):
# These flags are dynamically generated by the lre flake module.
#
# PATH=/nix/store/.../llvm-binutils-wrapper-20.1.1/bin:/nix/store/.../customClang/bin:...
# CC=/nix/store/.../customClang/bin/customClang
# Bazel-side configuration for LRE.
build --define=EXECUTOR=remote
build --extra_execution_platforms=@local-remote-execution//rust/platforms:aarch64-apple-darwin
build --extra_toolchains=@local-remote-execution//rust:rust-aarch64-darwin
build --extra_toolchains=@local-remote-execution//rust:rustfmt-aarch64-darwin
build --platforms=@local-remote-execution//rust/platforms:aarch64-apple-darwinNotice this registers the Rust platform even though the flake
requested the lre-cc Env — there's no local aarch64-darwin config
under generated-cc, so the module falls back to the toolchain that
does have one. The template's .bazelrc separately adds
--extra_execution_platforms=@//platforms:lre-cc for the C++ example,
which is the container-based platform from the callout above.
5. Point user.bazelrc at a real cache and executor
The template's own generator writes user.bazelrc with the literal
word TODO as every value — this isn't a placeholder left in this
page, it's what nix flake init puts on disk for you to edit:
build --remote_cache=grpcs://TODO
build --bes_backend=grpcs://TODO
build --remote_timeout=600
build --remote_executor=grpcs://TODOReplace all three TODOs with either your
dev.nativelink.com credentials or your
own cluster's endpoint. Concretely, filled in for a plain local
cluster (no TLS, so grpc:// instead of grpcs://, and no BES
endpoint to point at) — this is the exact user.bazelrc used to
produce the verified output in step 4 above:
build --remote_cache=grpc://127.0.0.1:50051
build --remote_timeout=600
build --remote_executor=grpc://127.0.0.1:50051A dev.nativelink.com or self-hosted
cluster looks the same shape, just with grpcs:// and your cluster's
real hostnames in place of 127.0.0.1:50051, plus a bes_backend
line if you want build results streamed there.
If you build before editing user.bazelrc at all, Bazel fails fast on
the literal hostname TODO — here's the exact error that produces,
captured from a real run, so you recognize it as "I forgot to edit
user.bazelrc" rather than something else being wrong:
[8 / 10] Compiling src/hello-world.cpp; 0s remote, remote-cache
ERROR: BUILD.bazel:3:10: Compiling src/hello-world.cpp failed:
Failed to query remote execution capabilities: UNAVAILABLE: Unable to resolve host TODOEverything before that DNS failure worked correctly — the Nix
toolchain, the Bazel platform wiring, and the lre-cc execution
platform selection (0s remote, remote-cache means Bazel committed to
executing it remotely). Once real values replace TODO, this step
just works.
6. Build
bazel build hello-worldWith a real endpoint in user.bazelrc and a worker running the
nativelink-worker-lre-cc image available to it, this compiles
src/hello-world.cpp on that worker and returns the binary through
the same CAS your local Bazel reads from. Re-run and it's a cache hit.
No container-capable worker handy?
You don't need a full cluster to see the cache side of this working.
Local cache and executor starts a plain
NativeLink instance on localhost — enough to validate
remote_cache against real credentials before you also need a
worker that matches lre-cc's platform requirements.
Troubleshooting
lre.bazelrc has stale /nix/store paths
If cache hits between local and remote execution stop happening,
compare lre.bazelrc's paths against the toolchain configs in the
@local-remote-execution module at the commit pinned in your
MODULE.bazel / flake.nix. A version drift between the two is the
most common cause.
Adding LRE to an existing project, not a fresh one
The template above bootstraps a new project. For the flake-parts
module wiring, bazel_dep override, and try-import setup to add
the same toolchains to a project you already have, see
local-remote-execution/README.md
— it also covers verifying the setup against a Kubernetes cluster.
What's next
- Explanations → LRE — the conceptual write-up.
- Local cache and executor — validate the protocol side without Nix or a container-capable worker.
- Nix templates — what else ships in the
bazelflake template. - Deployment → Kubernetes — running a real
worker fleet, including
lre-ccworkers, in production.