# How to Configure Multiple Backends with Stable Selector IDs and Route Exec Calls in Cloudflare Computer

> Configure multiple Cloudflare Computer backends using stable selector IDs and route exec calls. Learn how to map IDs and specify backends for targeted executions.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: how-to-guide
- Published: 2026-08-14

---

**Use explicit `id` values when creating backends, register them in a map passed to the `Workspace` constructor, then reference those IDs in the `backend` field of `ExecOptions` to route `exec` calls to specific runtimes.**

Cloudflare Computer lets a single `Workspace` manage multiple execution environments—containers, Worker shells, or JavaScript isolates—simultaneously. Each backend is identified by a **stable selector ID** that you control, enabling deterministic routing of commands via `Workspace.runtime.exec`. This guide walks through the configuration pattern implemented in [`cloudflare/computer`](https://github.com/cloudflare/computer), referencing actual source files from the repository.

## Understanding Backend Selectors and Routing

The routing mechanism centers on the `WorkspaceBackend` interface defined in [[`backend.ts`](https://github.com/cloudflare/computer/blob/main/backend.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backend.ts#L38-L48). Every backend exposes an `id` property that serves as its unique selector within a workspace.

```ts
// From backend.ts - the core interface
interface WorkspaceBackend {
  id: string;
  connect(host: string): Promise<BackendHandle>;
  // ... other properties
}

```

When you call `ws.runtime.exec()`, the workspace looks up the backend by the `backend` selector, lazily establishes a connection via `connect(host)`, caches the resulting `BackendHandle`, and forwards the command to the appropriate RPC stub. This lookup happens in the `Workspace` class implementation in [[`workspace.ts`](https://github.com/cloudflare/computer/blob/main/workspace.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts).

## Step 1: Create Backends with Explicit IDs

Always pass an `id` option to backend constructors. If omitted, a default ID is generated—but explicit IDs guarantee **stability across restarts** and prevent accidental mismatches when backend configurations change.

```ts
import { ContainerBackend, WorkerShellBackend } from "@cloudflare/computer";

// Explicit, stable selector IDs
const container = new ContainerBackend({ id: "container" });
const shell = new WorkerShellBackend({ id: "worker-shell" });

```

The `ContainerBackend` and `WorkerShellBackend` implementations set default IDs (`container-shell` and `worker-shell` respectively) when none is provided, as seen in their source definitions.

## Step 2: Register Backends in the Workspace Constructor

Pass a map of `id → backend` to the `Workspace` constructor. The workspace owns this map and **rejects duplicate IDs** to ensure each selector resolves unambiguously.

```ts
const ws = new Workspace({
  backends: {
    container,           // key matches backend.id
    "worker-shell": shell,  // key matches backend.id
  },
});

```

This registration pattern is enforced by the `Workspace` constructor in [[`workspace.ts`](https://github.com/cloudflare/computer/blob/main/workspace.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts). The map keys must match the `id` properties of the backend instances.

## Step 3: Route Exec Calls Using the Backend Selector

The `ExecOptions` type in [[`shell.ts`](https://github.com/cloudflare/computer/blob/main/shell.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/shell.ts) includes an optional `backend` field. When provided, the workspace routes the command to the matching backend.

```ts
// Route to container backend
await ws.runtime.exec("ls -la /", { backend: "container" });

// Route to Worker shell backend
await ws.runtime.exec("echo $HOME", { backend: "worker-shell" });

```

The `runtime.exec` API exposed in [[`client.ts`](https://github.com/cloudflare/computer/blob/main/client.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/client.ts) handles this routing transparently—your code simply specifies the target backend by its stable ID.

## Step 4: Configure Callable Backends (Optional)

Set `callable: true` in backend options to enable structured input/result pairs beyond raw shell commands. This flag is respected by the backend implementation and adds a higher-level RPC interface.

```ts
// Callable backends process JavaScript modules
await ws.runtime.exec(
  "module.exports = (input) => ({ ok: true, value: input * 2 });",
  { backend: "worker-shell" }
);

```

The same `backend` selector routing applies—only the execution semantics differ based on the backend's `callable` configuration.

## Step 5: Manage Backend Lifecycle

Backend handles are cached per workspace lifetime. Call `backendHandle.close()` to release resources, or rely on `Workspace.close()` to clean up all backends automatically.

```ts
// Clean up all backends
await ws.close();

```

This ensures proper teardown of container processes, RPC stubs, and other runtime resources.

## Complete Working Example

```ts
import {
  Workspace,
  ContainerBackend,
  WorkerShellBackend,
} from "@cloudflare/computer";

// 1. Create backends with stable selector IDs
const container = new ContainerBackend({ id: "container" });
const shell = new WorkerShellBackend({ id: "worker-shell" });

// 2. Register in workspace constructor
const ws = new Workspace({
  backends: {
    container,
    "worker-shell": shell,
  },
});

// 3. Route exec calls to specific backends
await ws.runtime.exec("apt-get update", { backend: "container" });
await ws.runtime.exec("curl https://api.example.com", { backend: "worker-shell" });

// 4. Clean up
await ws.close();

```

## Key Source Files Reference

| File | Purpose |
|------|---------|
| [[`backend.ts`](https://github.com/cloudflare/computer/blob/main/backend.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backend.ts) | `WorkspaceBackend` interface with `id` selector |
| [[`workspace.ts`](https://github.com/cloudflare/computer/blob/main/workspace.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) | `Workspace` class implementing backend resolution and routing |
| [[`shell.ts`](https://github.com/cloudflare/computer/blob/main/shell.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/shell.ts) | `ExecOptions` type with `backend` field |
| [[`client.ts`](https://github.com/cloudflare/computer/blob/main/client.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/client.ts) | High-level `runtime.exec` API |

## Summary

- **Explicit IDs**: Pass `id` to backend constructors for stable selectors across deployments
- **Registration map**: Provide `backends: { [id]: backend }` to `Workspace` constructor; duplicates are rejected
- **Route via `backend` field**: Use `ExecOptions.backend` in `runtime.exec()` calls to target specific runtimes
- **Lifecycle management**: Close workspace or handles to release resources properly

## Frequently Asked Questions

### What happens if I omit the `backend` option in an `exec` call?

If no `backend` selector is provided, the workspace uses a default backend—typically the first registered or a system-defined fallback. Explicit selection is recommended for predictable behavior when multiple backends are configured.

### Can I change a backend's ID after creating the workspace?

No. Backend IDs are fixed at construction time and validated during `Workspace` initialization. To use different IDs, create new backend instances and construct a fresh workspace.

### How does the workspace handle backend connection failures?

Connections are established lazily via `connect(host)` and cached as `BackendHandle` instances. If a connection fails, the error propagates through the `exec` call. The workspace does not automatically retry or failover to other backends—your application code must handle those semantics.

### Is the `backend` selector validated at compile time or runtime?

The selector is a string validated at runtime against registered backend IDs. TypeScript users can define union types for stricter checking, but the `Workspace` constructor performs runtime validation to reject duplicate or missing backend references.