NativeLink
Testing Remote Execution

Local cache and executor

Run a full NativeLink cache and executor on your own machine and prove Bazel's remote caching and remote execution work — no Nix, no shared cluster.

Before pointing a build at a shared cluster — or before setting up Local Remote Execution — prove the client side works against something you fully control: a NativeLink cache and executor running on your own machine, using whatever toolchain is already on your PATH. Nothing here talks to any external or shared service, and nothing here requires Nix.

This is not LRE

Local Remote Execution is a specific NativeLink feature: Nix-pinned toolchains that make local and remote actions hash-identical. This page is narrower and has no toolchain opinion at all — it's the fastest way to confirm a Bazel client can talk to a real NativeLink Execute and CAS implementation before you add Nix into the mix.

What you get

One nativelink process, one port for the client, a private port for the worker:

Bazel's --remote_cache and --remote_executor both point at 127.0.0.1:50051. 50061 only exists so the worker can register with the scheduler — Bazel never talks to it.

bazel build //:nativelink
# or: cargo build --bin nativelink

2. Grab the config

nativelink-config/examples/local_rbe_self_test.json5 is a complete, tested cluster — CAS, AC, a simple scheduler, and one local worker, entirely on localhost:

{
  stores: [
    { name: "CAS_MAIN_STORE", filesystem: { /* ... */ } },
    { name: "AC_MAIN_STORE", filesystem: { /* ... */ } },
    {
      // The worker's fast tier and the client-facing CAS share one store,
      // so anything the worker produces is immediately visible to Bazel.
      name: "WORKER_FAST_SLOW_STORE",
      fast_slow: {
        fast: { filesystem: { /* ... */ } },
        slow: { ref_store: { name: "CAS_MAIN_STORE" } },
      },
    },
  ],
  schedulers: [{
    name: "MAIN_SCHEDULER",
    simple: { supported_platform_properties: { cpu_count: "minimum" /* ... */ } },
  }],
  workers: [{
    local: {
      worker_api_endpoint: { uri: "grpc://127.0.0.1:50061" },
      cas_fast_slow_store: "WORKER_FAST_SLOW_STORE",
      upload_action_result: { ac_store: "AC_MAIN_STORE" },
      platform_properties: { cpu_count: { values: ["1"] } /* ... */ },
    },
  }],
  servers: [
    {
      // Cache AND execution on one client-facing port.
      name: "local",
      listener: { http: { socket_address: "0.0.0.0:50051" } },
      services: {
        cas: [{ cas_store: "CAS_MAIN_STORE" }],
        ac: [{ ac_store: "AC_MAIN_STORE" }],
        bytestream: [{ cas_store: "CAS_MAIN_STORE" }],
        execution: [{ cas_store: "CAS_MAIN_STORE", scheduler: "MAIN_SCHEDULER" }],
        capabilities: [{ remote_execution: { scheduler: "MAIN_SCHEDULER" } }],
      },
    },
    {
      // Private — only the worker connects here.
      name: "worker_api",
      listener: { http: { socket_address: "0.0.0.0:50061" } },
      services: { worker_api: { scheduler: "MAIN_SCHEDULER" }, health: {} },
    },
  ],
}

3. Start it

nativelink ./nativelink-config/examples/local_rbe_self_test.json5
INFO nativelink: Ready, listening on 0.0.0.0:50051
INFO nativelink: Ready, listening on 0.0.0.0:50061
INFO nativelink_worker::local_worker: Worker registered with scheduler, worker_id: 1f175824-...

4. Point Bazel at it

bazel test \
  --remote_cache=grpc://127.0.0.1:50051 \
  --remote_executor=grpc://127.0.0.1:50051 \
  --remote_default_exec_properties=cpu_count=1 \
  //your:target

This repo tests itself exactly this way — see build:self_test and build:self_execute in .bazelrc, combined as bazel test --config=self_test --config=self_execute. Copy that pattern into your own .bazelrc once the raw flags work.

5. Verify

First run — nothing cached yet, both actions run remote:

[4 / 5] 1 / 1 tests; Testing //:dummy_test; 0s remote, remote-cache
INFO: 5 processes: 3 internal, 3 remote.
//:dummy_test                                                    PASSED in 0.4s

bazel clean, run again — the genrule is a cache hit; the test re-executes remotely because --nocache_test_results was set:

[7 / 8] Testing //:dummy_test; 0s remote
INFO: 8 processes: 2 remote cache hit, 6 internal, 1 remote.
//:dummy_test                                                    PASSED in 0.5s

Both runs were captured against the exact config above, on macOS (arm64) — no Linux, no containers, no Nix.

Troubleshooting

What's next

On this page