Run a scheduler and a worker on your own machine, point Bazel at them, and watch a build action execute somewhere other than where you typed the command.
Who this is for: anyone with a working cache who wants to see remote
execution actually run. What you'll have at the end: a build whose
actions Bazel reports as remote, running against a scheduler and worker
you started yourself. Time: about fifteen minutes.
Before you start
A cache serving hits to your build. If you don't have one yet, start with Getting started.
The goal here is deliberately small: prove the protocol works before you
touch anything else. Everything runs on your machine, in one process, using
the toolchain already on your PATH. No containers to build, no fleet to
provision, no hermeticity questions yet; those come next, and they're much
easier to debug once you know the wiring is sound.
What you need
A Bazel workspace you can build (any one will do; the NativeLink repo
itself works), and either Docker or Nix. Up to about 4 GB of free disk
under /tmp for the caches this creates (the three filesystem stores in
the config cap at 2 GB, 200 MB, and 2 GB).
The worker connects outward to the scheduler on 50061 a moment after
startup. In the server's log you should see it announce itself before you
run anything, at the default INFO level:
If instead the log repeats Error with a connection failure every half
second, the worker cannot reach the worker_api listener; check that the
servers block in the config you downloaded still has the 50061 listener.
NativeLink does not serve gRPC reflection, so grpcurl ... list is not a
way to check which services are up; the config file is.
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
Both flags point at the same port here, because this config puts the cache
and the execution service on the same listener. In a real deployment they're
usually different processes and different ports; the flags don't change.
--remote_default_exec_properties=cpu_count=1 is worth passing even though
this config would accept an action without it. The scheduler here declares
cpu_count as a minimum property and the worker advertises exactly 1;
an action that asks for nothing matches any worker and consumes none of
that capacity, which happens to work here, but declaring it explicitly is
the habit that will save you on the next page.
That single word is the whole tutorial. --nocache_test_results forces the
action to actually execute instead of being served from the action cache,
which is what you want the first time; otherwise a cache hit will look
like a success and tell you nothing about whether execution works.
You did it right if
The server's log shows Worker registered with scheduler before you run
the build.
The build event log reports strategy as remote, not local or
worker.
The server's log shows Executing command from the in-process worker, so
the action left the queue rather than sitting in it.
Running the same build again without--nocache_test_results completes
near-instantly: the worker's output went into your action cache, so the
second run is a cache hit.
That last one is the point of the whole reading path in miniature: remote execution
doesn't replace your cache, it fills it. Every action a worker completes
becomes a hit for whoever asks next.
Four processes' worth of work happened inside one:
Bazel uploaded the action's inputs to the CAS and asked the
execution service to run it.
The execution service handed the action to the scheduler, which
compared what the action asked for (cpu_count=1) against what the
registered worker advertised (cpu_count: 1) and found a match.
The worker fetched the inputs from the CAS by digest, hardlinked them
into a sandbox under its work_directory, and ran the command.
The worker uploaded the outputs back to the CAS and the result to the
action cache, and the scheduler streamed completion back to Bazel.
Everything after this is that same loop with more machines in it.