> ## Documentation Index
> Fetch the complete documentation index at: https://namespace.so/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Bazel Remote Execution

export const BazelRbeDiagram = () => {
  const NamespaceMark = ({size = 32}) => <img className="ns-diagram-mark" src="/docs/favicon.svg" alt="" width={size} height={size} />;
  const ClientMark = ({size = 32}) => <span className="ns-diagram-mark ns-diagram-mark-terminal" style={{
    width: size,
    height: size
  }}>
			<svg width={Math.round(size * 0.55)} height={Math.round(size * 0.55)} viewBox="0 0 24 24" fill="none" aria-hidden="true">
				<rect x="3" y="4" width="18" height="16" rx="2" stroke="currentColor" strokeWidth="1.8" />
				<path d="M7 10l3 3-3 3M13 16h4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
			</svg>
		</span>;
  const SchedulerMark = ({size = 32}) => <span className="ns-diagram-mark ns-diagram-mark-control" style={{
    width: size,
    height: size
  }}>
			<svg width={Math.round(size * 0.55)} height={Math.round(size * 0.55)} viewBox="0 0 24 24" fill="none" aria-hidden="true">
				<circle cx="12" cy="6" r="2.5" fill="currentColor" />
				<circle cx="5" cy="18" r="2" fill="currentColor" />
				<circle cx="12" cy="18" r="2" fill="currentColor" />
				<circle cx="19" cy="18" r="2" fill="currentColor" />
				<path d="M12 8.5v3m0 0H5v3m7-3h7v3m-7-3v3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
			</svg>
		</span>;
  const WorkerMark = ({size = 32}) => <span className="ns-diagram-mark ns-diagram-mark-worker" style={{
    width: size,
    height: size
  }}>
			<svg width={Math.round(size * 0.55)} height={Math.round(size * 0.55)} viewBox="0 0 24 24" fill="none" aria-hidden="true">
				<rect x="6" y="6" width="12" height="12" rx="2" stroke="currentColor" strokeWidth="1.6" />
				<path d="M9.5 3v3m5-3v3m-5 12v3m5-3v3M3 9.5h3m-3 5h3m12-5h3m-3 5h3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
			</svg>
		</span>;
  const StorageMark = ({size = 32}) => <span className="ns-diagram-mark ns-diagram-mark-control" style={{
    width: size,
    height: size
  }}>
			<svg width={Math.round(size * 0.55)} height={Math.round(size * 0.55)} viewBox="0 0 24 24" fill="none" aria-hidden="true">
				<ellipse cx="12" cy="6" rx="7" ry="2.8" stroke="currentColor" strokeWidth="1.6" />
				<path d="M5 6v6c0 1.5 3.1 2.8 7 2.8s7-1.3 7-2.8V6" stroke="currentColor" strokeWidth="1.6" />
				<path d="M5 12v6c0 1.5 3.1 2.8 7 2.8s7-1.3 7-2.8v-6" stroke="currentColor" strokeWidth="1.6" />
			</svg>
		</span>;
  const DiagramCard = ({mark, label, sublabel, brand = false}) => <div className={`ns-diagram-card${brand ? " ns-diagram-card-brand" : ""}`}>
			{mark}
			<div className="ns-diagram-card-copy">
				<span className="ns-diagram-card-label">{label}</span>
				<span className="ns-diagram-card-sublabel">{sublabel}</span>
			</div>
		</div>;
  const ZoneHeader = ({owner, mark}) => <div className="ns-diagram-zone-header">
			{mark}
			<span>{owner}</span>
		</div>;
  const Zone = ({brand = false, children}) => <section className={`ns-diagram-zone${brand ? " ns-diagram-zone-brand" : ""}`}>
			{children}
		</section>;
  const VerticalConnector = ({label, dashed = false, minHeight = 70}) => <div className={`ns-diagram-vertical-connector${dashed ? " ns-diagram-connector-dashed" : ""}`} style={{
    minHeight
  }} aria-hidden="true">
			<div className="ns-diagram-vertical-line" />
			<div className="ns-diagram-arrow-down" />
			<span>{label}</span>
		</div>;
  const label = "A Bazel client submits actions through the Remote Execution API to Namespace, where the scheduler assigns work to workers that exchange inputs and outputs with shared storage.";
  return <figure className="ns-diagram not-prose" role="img" aria-label={label}>
			<div className="ns-diagram-grid-background" aria-hidden="true" />
			<div className="ns-diagram-content">
				<Zone>
					<ZoneHeader owner="Your build" mark={<ClientMark size={14} />} />
					<DiagramCard mark={<ClientMark size={36} />} label="Bazel" sublabel="Running on your machine, in CI, or in a Devbox" />
				</Zone>
				<div className="ns-bazel-connector-inset">
					<VerticalConnector label="Remote Execution API" />
				</div>
				<Zone brand>
					<ZoneHeader owner="Namespace" mark={<NamespaceMark size={14} />} />
					<DiagramCard brand mark={<SchedulerMark size={36} />} label="Scheduler" sublabel="Accepts requests and assigns actions to workers" />
					<div className="ns-bazel-connector-inset">
						<VerticalConnector label="assigns actions" dashed minHeight={56} />
					</div>
					<div className="ns-bazel-workers">
						<DiagramCard brand mark={<WorkerMark size={36} />} label="Worker" sublabel="4 execution slots" />
						<DiagramCard brand mark={<WorkerMark size={36} />} label="Worker" sublabel="4 execution slots" />
						<DiagramCard brand mark={<WorkerMark size={36} />} label="Worker" sublabel="4 execution slots" />
					</div>
					<div className="ns-bazel-connector-inset">
						<VerticalConnector label="action inputs & outputs" minHeight={56} />
					</div>
					<DiagramCard brand mark={<StorageMark size={36} />} label="Storage" sublabel="Content-addressable storage, shared with the Bazel cache" />
				</Zone>
			</div>
			<figcaption className="ns-diagram-caption">{label}</figcaption>
		</figure>;
};

Run Bazel actions on Namespace compute instead of on the machine driving the
build.

<Info>
  Remote Execution is now **generally available**.
</Info>

Namespace runs your Bazel actions on workers it keeps running for your
workspace, so a build can spread across many workers without waiting for them to
start. Actions can also run on a platform other than the one driving Bazel,
which is how a Linux host can build and test macOS artifacts.

Remote Execution builds on the [Namespace Bazel cache](/docs/bazel/cache). The
configuration written by `nsc bazel setup` includes both the remote executor and
the remote cache, so remotely executed actions share artifacts with your CI and
local builds.

## Getting started

Remote Execution is enabled for every workspace. Use
[`nsc bazel setup`](/docs/reference/cli/bazel-setup) to create a Bazel
configuration file for the execution cluster:

<Steps titleSize="h3">
  <Step title="Configure execution access">
    ```bash theme={null}
    nsc bazel setup --bazelrc=~/.namespace.bazelrc
    ```

    This command provisions the scheduler and storage components if needed and writes
    a Bazel configuration file with the remote executor, remote cache, credentials,
    and recommended execution defaults.
  </Step>

  <Step title="Run Bazel with remote execution">
    ```bash theme={null}
    bazel --bazelrc=~/.namespace.bazelrc build //...
    ```

    You can pass multiple `--bazelrc` flags if you need to combine Namespace's
    generated configuration with your existing project configuration.
  </Step>
</Steps>

## How it works

A Bazel Remote Execution cluster has three components:

* **Scheduler**: accepts Bazel Remote Execution API requests and assigns actions to workers.
* **Storage**: stores content-addressable storage (CAS) entries durably across the cluster.
* **Workers**: execute Bazel actions.

<BazelRbeDiagram />

Each worker advertises a fixed number of execution slots. A slot represents one
unit of concurrent work, so a worker with four slots can run up to four actions
at the same time. Namespace adds workers as a build requires more capacity.

## Example: Linux host to Linux workers

This example runs Bazel from a Linux host and executes actions on
Linux workers. It uses a public Bazel repository so you can try the flow without
changing your own project first.

```bash theme={null}
git clone https://github.com/bazelbuild/examples.git
cd examples/cpp-tutorial/stage3

nsc bazel setup --bazelrc=~/.namespace.bazelrc --key=examples-linux
bazel --bazelrc=~/.namespace.bazelrc build //...
```

Actions without explicit platform properties are routed to Linux workers.

## Example: Linux host to macOS workers

Bazel can also route selected actions to macOS workers from a Linux client. The
[linux-to-mac-rbe-demo](https://github.com/namespacelabs/linux-to-mac-rbe-demo)
repository demonstrates that setup.

This example requires `macos/arm64` workers to be enabled for your workspace. If
you see an error such as `no on-demand worker capacity configured for platform "macos/arm64"`, reach out to the Namespace team to enable macOS worker capacity.

```bash theme={null}
git clone https://github.com/namespacelabs/linux-to-mac-rbe-demo.git
cd linux-to-mac-rbe-demo

nsc bazel setup --bazelrc=~/.namespace.bazelrc --key=linux-to-mac-demo
bazelisk --bazelrc=~/.namespace.bazelrc build //DemoApp:DemoApp
```

For your own targets, route an action to macOS by setting Bazel execution
properties on that target or platform, for example:

```python theme={null}
exec_properties = {
    "OSFamily": "macos",
    "Arch": "arm64",
}
```

Actions without macOS execution properties continue to run on the default Linux
workers, allowing one Bazel invocation to use both platforms.

## Remote Execution from a Devbox

You can enable Bazel Remote Execution on any [Devbox](/docs/devbox), including
one that already exists. Run setup from inside the Devbox, whether or not it was
created with [Bazel caching](/docs/bazel/cache#running-bazel-from-a-devbox)
enabled.

<Steps titleSize="h3">
  <Step title="Create a Devbox">
    Point `--checkout` at the repository that holds your Bazel workspace, so the
    Devbox starts with the project already cloned:

    ```bash theme={null}
    devbox create --checkout=github.com/your-org/your-repo
    ```

    Skip this step if you already have a Devbox with your Bazel project checked out.
  </Step>

  <Step title="Open a shell in the Devbox">
    ```bash theme={null}
    devbox ssh my-devbox
    ```

    Run the remaining steps inside the Devbox, not on your own machine.
  </Step>

  <Step title="Enable remote execution">
    ```bash theme={null}
    nsc bazel setup --bazelrc=/home/devbox/.bazelrc
    ```

    Remote execution `--remote=true` is on by default, so this writes both
    the remote executor and the remote cache into the configuration. Pass
    `--remote=false` for a cache-only setup.

    <Info>
      Devboxes store the path to the `bazelrc` in the `BAZELRC` environment variable, which lets scripts and later examples name the configuration without hardcoding the path. Check that it is set:

      ```bash theme={null}
      echo $BAZELRC
      ```

      If that comes back empty, set it yourself with:

      ```bash theme={null}
      echo 'export BAZELRC="/home/devbox/.bazelrc"' >> ~/.bashrc
      ```
    </Info>
  </Step>

  <Step title="Run Bazel">
    ```bash theme={null}
    bazel build //...
    ```

    `/home/devbox/.bazelrc` is the `devbox` user's home `bazelrc`, so Bazel picks it
    up with no flags.
  </Step>
</Steps>

Keep repository-specific execution settings in a `bazelrc` checked into your
repository rather than editing the generated one, since setup overwrites it each
time it runs. Combine the two with repeated `--bazelrc` flags, using the
`BAZELRC` environment variable that the Devbox sets to the generated file:

```bash theme={null}
bazel --bazelrc=$BAZELRC --bazelrc=tools/bazelrc/rbe.bazelrc build //...
```

## Remote Execution from CI

<Info>
  Authenticating Remote Execution with a revocable token requires **nsc v0.0.544** or later. Run `nsc version ensure --at_least 0.0.544` to check and update the CLI if needed.
</Info>

By default, `nsc bazel setup` uses your interactive login and writes a
bazelrc that authenticates to the cluster with a short-lived mTLS client
certificate. For CI/CD pipelines, automation, or any environment without an
interactive login, you can instead authenticate with a
[revocable token](/docs/reference/cli/token-create).

Revocable tokens are long-lived but can be revoked at any time from
[cloud.namespace.so/user/sessions](https://cloud.namespace.so/user/sessions) or
with [`nsc token revoke`](/docs/reference/cli/token-revoke).

<Steps titleSize="h3">
  <Step title="Create a revocable token">
    Use [`nsc bazel create-token`](/docs/reference/cli/bazel-create-token) to create
    a token with the permissions required for Remote Execution and write it to a
    file:

    ```bash theme={null}
    nsc bazel create-token \
      --token rbe-token.json \
      --expires_in 10d
    ```

    Set `--expires_in` to a duration of up to 365 days.
  </Step>

  <Step title="Generate a bazelrc with the token">
    Pass the token file to the setup command. This provisions a cluster that uses
    the revocable token to obtain short-lived credentials:

    ```bash theme={null}
    nsc bazel setup --token rbe-token.json --bazelrc=namespace.bazelrc
    ```
  </Step>

  <Step title="Run Bazel with remote execution">
    ```bash theme={null}
    bazel --bazelrc=namespace.bazelrc build //...
    ```
  </Step>
</Steps>

## Additional configuration options

Bazel execution properties can reserve worker CPU capacity and select the isolation mode on Linux workers:

```python theme={null}
exec_properties = {
    "cpu": "2",
    "namespace_action_isolation": "sandboxed",
    "namespace_requires_network": "true",
}
```

Use `--remote_default_exec_properties` to apply a property to actions that do
not override it:

```bash theme={null}
bazel --bazelrc=~/.namespace.bazelrc build //... \
  --remote_default_exec_properties=cpu=2 \
  --remote_default_exec_properties=namespace_action_isolation=sandboxed \
```

### Reserve CPU capacity

The `cpu` property tells the scheduler how much CPU capacity to
reserve while an action runs. Values can be whole or fractional numbers, such
as `"2"` or `"0.5"`. The scheduler considers both execution slots and CPU
reservations when assigning concurrent actions to a worker.

The reservation is a scheduling input, not a CPU limit applied to the action.
An omitted or zero value does not reserve CPU capacity beyond the action's
execution slot.

`resources:cpu` is accepted as an alternative property name.

### Select action isolation

The `namespace_action_isolation` property controls how a Linux worker runs an
action:

| Value       | Behavior                                                                                      |
| ----------- | --------------------------------------------------------------------------------------------- |
| `sandboxed` | Runs the action in an isolated filesystem, process, network, mount, UTS, and IPC environment. |
| `none`      | Runs the action directly in the worker environment without additional per-action sandboxing.  |
| Omitted     | Uses the workspace default setting.                                                           |

The workspace default is `none` unless configured otherwise.
Sandboxed isolation is supported only on Linux workers at the moment.
Contact [support@namespace.so](mailto:support@namespace.so) to change the setting.

Sandboxed actions receive an isolated, loopback-only network namespace by
default. Set `namespace_requires_network` to `true` on an action that needs
network access. The action keeps its filesystem, process, mount, UTS, and IPC
isolation, but shares the worker's network namespace. Omit the property for
actions that do not need network access.

## Custom worker pools

Named worker pools let actions select a worker machine type and custom base
image.
Namespace creates a pool when its first action is queued, starts workers for it
on demand, and routes only actions that name that pool to those workers. Actions
without a pool use the workspace's default workers.

To use custom worker pools, define each pool as a Bazel execution platform. The
platform's `exec_properties` describe the properties of the pool. A constraint
value then lets targets select that execution platform with
`exec_compatible_with`:

```python theme={null}
constraint_setting(name = "worker_pool")

constraint_value(
    name = "compile_pool_constraint",
    constraint_setting = ":worker_pool",
)

constraint_value(
    name = "high_memory_pool_constraint",
    constraint_setting = ":worker_pool",
)

platform(
    name = "compile_pool",
    constraint_values = [":compile_pool_constraint"],
    exec_properties = {
        "namespace_pool": "compile",
        "namespace_pool_machine_type": "linux/amd64:16x32",
    },
)

platform(
    name = "high_memory_pool",
    constraint_values = [":high_memory_pool_constraint"],
    exec_properties = {
        "namespace_pool": "high-memory",
        "namespace_pool_machine_type": "linux/amd64:16x64",
        "namespace_pool_slots": "2",
    },
)

genrule(
    name = "compile_probe",
    outs = ["compile_probe.txt"],
    cmd = "printf compile > $@",

    exec_compatible_with = [":compile_pool_constraint"],
)

genrule(
    name = "high_memory_probe",
    outs = ["high_memory_probe.txt"],
    cmd = "printf high-memory > $@",

    exec_compatible_with = [":high_memory_pool_constraint"],
)
```

Register the pool platforms when invoking Bazel:

```bash theme={null}
bazel --bazelrc=~/.namespace.bazelrc build //... \
  --extra_execution_platforms=//:compile_pool,//:high_memory_pool
```

You can add `--extra_execution_platforms` to your project bazelrc instead of
passing it on every invocation.

The machine type uses the format `<os>/<arch>:<cpu>x<memory>`. For example,
`linux/amd64:16x64` selects a Linux AMD64 worker with 16 vCPUs and 64 GB of
memory. See [Machine Shapes](/docs/architecture/compute/machine-shapes) for
supported CPU and memory configurations.

The following execution properties define a pool:

| Property                      | Description                                                                                                               |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `namespace_pool`              | Required property that names the pool. Use different names for different configurations.                                  |
| `namespace_pool_machine_type` | Required property of the format `<os>/<arch>:<cpu>x<memory>`. Examples include `linux/arm64:8x16` and `macos/arm64:6x14`. |
| `namespace_pool_worker_image` | Overrides the worker base image. When omitted, the pool will use the default worker image.                                |

Every execution platform that uses the same pool name must provide the same pool
definition. The scheduler rejects an action if its machine type or worker image
conflicts with the pool's existing definition.

## Custom worker base images

By default, actions run using one of Namespace's managed worker images. If your
actions need extra tools, libraries, or a specific base environment, you can run
them inside your own container image instead.

Custom images are selected per action through the standard Bazel
`container-image` execution property.

A few requirements apply:

* The image reference **must be pinned to an immutable digest**
  (`repo@sha256:...`). Tags are rejected.
* The image **must be optimized** before it can be used. Namespace converts the
  image into a fast-booting disk variant.
* The worker image must be hosted in the Namespace container registry `nscr.io`.
* Custom images are supported on Linux workers only.

### Build, push, and optimize a custom image

<Steps titleSize="h3">
  <Step id="build-and-push-your-image" title={<span>Build and push your image</span>}>
    Write a `Dockerfile` that starts from any Linux base and adds the tools your
    actions need. Authenticate against `nscr.io` with
    [`nsc docker login`](/docs/reference/cli/docker-login), then build and push it
    with [`nsc build`](/docs/reference/cli/build) for the worker architecture
    (`linux/amd64` or `linux/arm64`):

    ```bash theme={null}
    nsc docker login
    nsc build . -t nscr.io/<tenant>/my-worker:latest --platform linux/amd64 --push
    ```

    Already have an image elsewhere? Use
    [`nsc base-image upload`](/docs/reference/cli/base-image-upload) to pull it and
    push it into your `nscr.io` registry instead:

    ```bash theme={null}
    nsc base-image upload <source-image> my-worker:latest
    ```
  </Step>

  <Step id="pin-it-to-an-immutable-digest" title={<span>Pin it to an immutable digest</span>}>
    The scheduler requires a digest, not a tag. Resolve the digest your push produced:

    ```bash theme={null}
    docker inspect --format '{{index .RepoDigests 0}}' nscr.io/<tenant>/my-worker:latest
    nscr.io/<tenant>/my-worker@sha256:<digest>
    ```

    Use that `repo@sha256:<digest>` reference everywhere below.
  </Step>

  <Step id="optimize-the-image" title={<span>Optimize the image</span>}>
    Optimize the pinned image with
    [`nsc base-image optimize`](/docs/reference/cli/base-image-optimize). This step blocks until the optimized variant is ready:

    ```bash theme={null}
    nsc base-image optimize --image_ref nscr.io/<tenant>/my-worker@sha256:<digest>
    ```

    You only need to optimize a given digest once. Pushing a new image with a new digest requires optimizing that new digest.

    <Info>
      Optimizing a base image from a GitHub Actions job requires the job to run with additional permission grants. Pass the `baseimage:*:*` grant as a `permissions.additional_grant` feature setting on the runner.
    </Info>

    With a runner profile, append it to the profile name:

    ```yaml theme={null}
    jobs:
      rbe:
        runs-on:
          - namespace-profile-foobar;permissions.additional_grant=baseimage:*:*
    ```

    With a machine label, pass it through the `namespace-features:` label instead:

    ```yaml theme={null}
    jobs:
      rbe:
        runs-on:
          - nscloud-ubuntu-22.04-amd64-4x8-with-features
          - namespace-features:permissions.additional_grant=baseimage:*:*
    ```

    See
    [Configuring Features and Overrides](/docs/reference/github-actions/runner-configuration#configuring-features)
    for the full syntax of feature settings, and
    [Access Level](/docs/reference/github-actions/runner-configuration#access-level)
    for restricting what a runner can do.

    This grant is only needed when optimizing from a GitHub Actions job. Running
    `nsc base-image optimize` from a local machine outside of Namespace does not
    require it.
  </Step>

  <Step id="use-it-in-your-build" title={<span>Use it in your build</span>}>
    Point your actions at the custom worker image with the
    `container-image` execution property (see the next sections).
  </Step>
</Steps>

<Info>
  If you run a build against an image that has not been optimized yet, the scheduler rejects the affected actions with an error telling you the image is not optimized. Run `nsc base-image optimize` for that digest.
</Info>

### Use the custom image

Set the `container-image` execution property on a specific target or platform so
only those actions run with the custom worker image:

```python theme={null}
exec_properties = {
    "container-image": "docker://<repo>@sha256:<digest>",
}
```

The `docker://` scheme prefix is optional. To apply an image through a custom
worker pool instead, set `namespace_pool_worker_image` as described in
[Custom worker pools](#custom-worker-pools).

### Run preparation scripts at worker startup

A custom worker image can ship preparation scripts that run once before the
worker starts handling any actions. These hooks handle setup that must run on
every worker boot, such as mounting an external `/nix` store or seeding caches.

Place executable files in `/etc/namespace/prepare.d` inside your image. On
startup, the worker runs every executable file in that directory in lexical
order (e.g. `10-mount.sh` before `20-warmup.sh`). Non-executable files and
subdirectories are skipped.

<Info>
  Preparation scripts are **critical**: they run at the very start, and if any script exits non-zero the worker fails to start and handles no work.
</Info>

## Observability

Namespace records every invocation that runs through remote execution. Inspect
them from the [Bazel invocations](https://cloud.namespace.so/workspace/bazel/invocations)
page, or from the CLI with
[`nsc bazel invocation list`](/docs/reference/cli/bazel-invocation-list) and
[`nsc bazel invocation report`](/docs/reference/cli/bazel-invocation-report).

Reports carry worker assignment and timing, queue times, and hydration times
alongside the usual action and target data, which makes them the place to look
when a build fails for a reason Bazel cannot see.

See [Bazel observability](/docs/bazel/observability) for the full workflow,
including how to analyze a report with an agent.

Future reports will also include operation joins, which indicate whether an
invocation joins action invocations from other invocations, and hot or warm
cache information.

## Current caveats

Remote Execution has the following current limitations:

* Action isolation within macOS workers is not available yet.

If any of these caveats block your use case, contact [support@namespace.so](mailto:support@namespace.so)
so we can help find an appropriate solution.
