How to Register and Configure Multiple Backends with Stable IDs in a Cloudflare Computer Workspace

Pass an array of backend objects with explicit id selectors to the Workspace constructor, then route execution via workspace.runtime.exec(source, { backend: "your-id" }).

A Cloudflare Computer Workspace is the core abstraction that combines a SQLite-backed virtual filesystem with a pluggable execution surface. According to the cloudflare/computer source code, you can attach multiple backend implementations to a single workspace and address each one through a stable, persistent ID that survives restarts and lazy initialization.

Understanding Backend Handles and Stable IDs

In packages/computer/src/workspace.ts, the Workspace class accepts a backends array where each entry becomes a BackendHandle. This handle wraps the backend implementation with a selector id that the runtime uses for routing.

The ID provides three critical properties:

  • Deterministic routing — Callers reference backends by logical name rather than implementation details
  • Cross-session stability — The same ID resolves to the same backend configuration even after the Durable Object restarts
  • Implementation swapping — Replace the underlying backend without changing consumer code

Built-in Backends and Default IDs

The cloudflare/computer repository ships with three built-in backends, each with a default stable ID:

Backend Default ID Implementation File
WorkerShellBackend worker-shell packages/computer/src/backends/worker-shell.ts
ContainerBackend container packages/computer/src/backends/container.ts
WorkerJavascriptBackend worker-javascript packages/computer/src/backends/worker-javascript.ts

These defaults are defined in the respective backend constructors and used when you pass a bare backend instance without wrapping it in a handle object.

Registering Multiple Backends in a Workspace

Basic Registration with Default IDs

The simplest pattern passes backend instances directly. Each receives its default ID automatically:

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

const workspace = new Workspace({
  storage: ctx.storage,
  sessionId: ctx.id.toString(),
  backends: [
    new WorkerShellBackend({
      loader: env.LOADER,
      workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
      ctx,
    }),
    new ContainerBackend({
      // container-specific configuration
    }),
  ],
});

Custom IDs for Multiple Instances of the Same Backend

To register multiple instances of the same backend type—such as two isolated shells with different configurations—supply an explicit id by wrapping the backend in a handle object:

const workspace = new Workspace({
  storage: ctx.storage,
  sessionId: ctx.id.toString(),
  backends: [
    // First shell with default ID "worker-shell"
    new WorkerShellBackend({
      loader: env.LOADER,
      workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
      ctx,
    }),

    // Second shell with custom ID "debug-shell"
    {
      id: "debug-shell",
      backend: new WorkerShellBackend({
        loader: env.DEBUG_LOADER,
        workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
        ctx,
      }),
    },

    // Container with custom ID
    {
      id: "my-container",
      backend: new ContainerBackend({
        // container-specific options
      }),
    },
  ],
});

The BackendHandle structure (defined in packages/computer/src/workspace.ts) accepts:

  • id: string — your stable selector
  • backend: Backend instance — the actual implementation

Routing Execution to Specific Backends

Use workspace.runtime.exec() with the backend option to target a specific backend by its stable ID:

// Execute on the default worker-shell backend
await workspace.runtime.exec("echo hello", { backend: "worker-shell" });

// Execute on the custom debug shell
await workspace.runtime.exec("ls -la", { backend: "debug-shell" });

// Execute on the custom container backend
await workspace.runtime.exec("echo container", { backend: "my-container" });

The runtime performs lazy initialization: the backend is constructed or retrieved from cache on first use, but the ID-to-implementation mapping remains constant throughout the workspace lifetime.

Complete Durable Object Example

Here's a production-ready pattern from the cloudflare/computer documentation:

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

export class MyAgent extends DurableObject<Env> {
  #workspace: Workspace;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    this.#workspace = new Workspace({
      storage: ctx.storage,
      sessionId: ctx.id.toString(),
      backends: [
        // Worker shell with default ID
        new WorkerShellBackend({
          loader: env.LOADER,
          workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
          ctx,
        }),

        // Container with custom ID
        {
          id: "my-container",
          backend: new ContainerBackend({
            /* container options */
          }),
        },

        // JavaScript runtime with custom ID
        {
          id: "js-runtime",
          backend: new WorkerJavascriptBackend({
            /* JS runtime options */
          }),
        },
      ],
    });
  }

  async fetch(request: Request): Promise<Response> {
    const { pathname } = new URL(request.url);

    if (pathname === "/run-shell") {
      return this.#workspace.runtime.exec("echo hello", { backend: "worker-shell" });
    }
    if (pathname === "/run-container") {
      return this.#workspace.runtime.exec("echo container", { backend: "my-container" });
    }
    if (pathname === "/run-js") {
      return this.#workspace.runtime.exec(
        `export default () => "js"`,
        { backend: "js-runtime" }
      );
    }
    return new Response("not found", { status: 404 });
  }
}

Why Stable IDs Matter for Backend Configuration

Stable IDs in a Cloudflare Computer Workspace enable three architectural benefits:

  1. Logical decoupling — Your application code references "debug-shell" without knowing whether it runs in a worker, container, or future backend type
  2. Multi-tenancy within one workspace — Isolate different execution environments (production vs. debugging shells) under one SQLite filesystem
  3. Configuration versioning — Change backend parameters (different loader bindings, resource limits) while preserving the external API contract

The stability guarantee holds because the ID is bound at Workspace construction time in your Durable Object constructor, which executes consistently on every fetch.

Summary

  • Create backends by instantiating WorkerShellBackend, ContainerBackend, or WorkerJavascriptBackend
  • Assign stable IDs either by accepting defaults (worker-shell, container, worker-javascript) or wrapping in { id: string, backend: Backend } handles
  • Register in Workspace by passing the backends array to the constructor in packages/computer/src/workspace.ts
  • Route execution with workspace.runtime.exec(source, { backend: "your-id" }) — the runtime resolves IDs lazily but consistently

Frequently Asked Questions

What happens if two backends share the same ID?

The Workspace constructor in packages/computer/src/workspace.ts does not deduplicate IDs. If you register multiple backends with identical selectors, the last one in the array wins. Always use unique IDs when registering multiple instances.

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

No. Stable IDs are immutable after construction. The BackendHandle is frozen when passed to the Workspace constructor. To change IDs, you must create a new Workspace instance with updated configuration.

Do stable IDs persist across Durable Object migrations?

Yes. The ID is part of your code's construction logic, not runtime state. As long as your Durable Object constructor passes the same ID string, the backend selector remains stable even when the Durable Object migrates between Cloudflare edges or restarts.

How do I check which backends are available at runtime?

The cloudflare/computer source does not expose a public API for listing registered backends. Track your backend IDs in application code or environment configuration. The runtime will throw if you attempt to exec with an unregistered backend selector.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →