# How to Implement a Custom Execution Backend in Computer Beyond Built-In Options

> Implement a custom execution backend in Computer by creating a WorkspaceBackend class. Learn how to connect and return a BackendHandle with a WorkspaceRPC stub for advanced functionality.

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

---

**Implement a custom execution backend in Computer by creating a class that implements the `WorkspaceBackend` interface, returning a `BackendHandle` with a `WorkspaceRPC` stub from the `connect()` method.**

The **Computer SDK** abstracts execution environments behind a clean interface, letting you plug in any runtime—from remote VMs to sandboxed WASM environments. This guide walks through the exact contract defined in [`packages/computer/src/backend.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backend.ts) and shows how to register your implementation with a Workspace.

## Understanding the WorkspaceBackend Interface

Every custom backend must satisfy 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). The interface requires:

| Field/Method | Purpose |
|-------------|---------|
| `id: string` | Selector used by callers (e.g., `backend: "my-custom"`) |
| `type: string` | Diagnostic identifier for logging and debugging |
| `callable?: boolean` | Whether the backend accepts structured `input`/`result` |
| `sync?: "none" \| "local" \| "remote"` | SQLite VFS synchronization mode |
| `connect(host)` | Returns a `Promise<BackendHandle>` |

The **BackendHandle** you return must expose:

- `rpc: WorkspaceRPC` — the RPC stub used for `exec`, `push`, `pull`, and other operations
- `runtimeId?: string` — optional identifier for tracing
- `closed: Promise<void>` — resolves when the transport terminates
- `close(): Promise<void>` — cleans up resources

## Step-by-Step Implementation

### Define Your Backend Class

Create a TypeScript class implementing `WorkspaceBackend`. The `connect()` method is where you establish your transport—whether that's spawning a process, opening a WebSocket, or instantiating an in-process runtime.

The built-in **container backend** ([[`cloudflare-container.ts`](https://github.com/cloudflare/computer/blob/main/cloudflare-container.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/container/cloudflare-container.ts)) launches `computerd` and wires the Cap'n-Proto RPC driver from [[`packages/computerd/src/fuse/driver.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/fuse/driver.ts)](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/fuse/driver.ts). The **worker-shell backend** ([[`worker-shell.ts`](https://github.com/cloudflare/computer/blob/main/worker-shell.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/worker-shell/worker-shell.ts)) spawns a Just-Bash shim. Your implementation follows the same pattern with your transport of choice.

### Configure Synchronization Mode

Set `sync` based on your backend's storage architecture:

- **`"remote"`** (default): Your backend has its own SQLite VFS. The Workspace uses `push()`/`pull()` to synchronize state.
- **`"none"`**: Your backend operates on the host-side store directly (e.g., pure in-process mocks). No synchronization needed.

This affects how [[`workspace.ts`](https://github.com/cloudflare/computer/blob/main/workspace.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) handles file operations.

### Implement Lifecycle Hooks

Your `BackendHandle` must provide:

1. A `closed` promise that resolves when the connection drops
2. A `close()` method for explicit cleanup

The Workspace caches handles by `id` and reuses them across calls, so proper cleanup prevents resource leaks.

## Complete Custom Backend Example

Here's a minimal Docker-based backend following the Computer SDK contract:

```typescript
// src/backends/docker-custom.ts
import type { WorkspaceBackend, WorkspaceBackendHost, BackendHandle } from "@cloudflare/computer";
import type { WorkspaceRPC } from "@cloudflare/computer-rpc";

export interface DockerBackendOptions {
  /** Identifier used with `backend: "my-docker"` */
  id?: string;
  /** Docker image containing the runtime */
  image: string;
}

export class DockerBackend implements WorkspaceBackend {
  readonly id: string;
  readonly type = "docker-custom";
  readonly callable = false;
  readonly sync = "remote" as const;

  private readonly image: string;

  constructor(opts: DockerBackendOptions) {
    this.id = opts.id ?? "docker-custom";
    this.image = opts.image;
  }

  async connect(_: WorkspaceBackendHost): Promise<BackendHandle> {
    // Launch container with RPC endpoint
    const proc = await startDockerContainer(this.image);
    
    // Establish WebSocket transport
    const ws = new WebSocket(`ws://localhost:${proc.port}`);
    
    // Build RPC stub from transport
    const rpc: WorkspaceRPC = await buildWorkspaceRpcFromWebSocket(ws);
    
    // Lifecycle: resolve when connection ends
    const closed = new Promise<void>((resolve) => {
      ws.addEventListener("close", () => resolve());
      proc.on("exit", () => resolve());
    });

    return {
      rpc,
      runtimeId: proc.id,
      closed,
      async close() {
        ws.close();
        await proc.kill();
      },
    };
  }
}

// Replace with real Docker API calls
async function startDockerContainer(image: string) {
  // Returns { id, port, kill(), on(event, cb) }
  throw new Error("implementation omitted");
}

async function buildWorkspaceRpcFromWebSocket(ws: WebSocket): Promise<WorkspaceRPC> {
  // Use @cloudflare/computer-rpc client factory
  throw new Error("implementation omitted");
}

```

## Registering and Using Your Backend

Pass your backend instance to the Workspace constructor:

```typescript
import { Workspace } from "@cloudflare/computer";
import { DockerBackend } from "./backends/docker-custom";

const ws = new Workspace({
  storage: ctx.storage,
  backends: [
    new DockerBackend({ image: "myorg/my-runtime:latest" })
  ],
});

// Execute via your custom backend
await ws.runtime.exec("echo hello", { backend: "docker-custom" });

```

The Workspace validates that each `id` is unique and routes calls based on the `backend` option. If you set `callable: true`, callers can also use structured inputs and receive structured results.

## Reference Implementations

Use these source files as starting templates:

- **[[`packages/computer/src/backends/test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/test.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/test.ts)** — Minimal in-process backend, ideal for testing.
- **[[`packages/computer/src/backends/worker-shell/worker-shell.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/worker-shell/worker-shell.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/worker-shell/worker-shell.ts)** — Shows Just-Bash shim integration.
- **[[`packages/computer/src/backends/container/cloudflare-container.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/container/cloudflare-container.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/container/cloudflare-container.ts)** — Full production backend with `computerd` and Cap'n-Proto.
- **[[`docs/12_worker_backend.md`](https://github.com/cloudflare/computer/blob/main/docs/12_worker_backend.md)](https://github.com/cloudflare/computer/blob/main/docs/12_worker_backend.md)** — Documentation on the multiple-backend model.

## Summary

- Implement **`WorkspaceBackend`** from [`backend.ts`](https://github.com/cloudflare/computer/blob/main/backend.ts) with `id`, `type`, `sync`, and `connect()`.
- Return a **`BackendHandle`** containing a `WorkspaceRPC` stub, `closed` promise, and `close()` method.
- Choose **`sync: "remote"`** for separate VFS, **`sync: "none"`** for host-direct storage.
- Wire any transport in `connect()`—processes, WebSockets, containers, or WASM runtimes.
- Register with **`backends: [yourInstance]`** in `Workspace` constructor.
- Route execution with **`{ backend: "your-id" }`** in `ws.runtime.exec()`.

## Frequently Asked Questions

### What is the minimum required implementation for a custom Computer backend?

**The minimum is a class implementing `WorkspaceBackend` with `id`, `type`, and a `connect()` method returning `{ rpc, closed, close }`.** The `rpc` field must be a `WorkspaceRPC` stub. All other fields are optional. See [[`packages/computer/src/backends/test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/test.ts)](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/test.ts) for a working minimal example.

### How does the Workspace handle backend lifecycle and caching?

**The Workspace calls `connect()` once per `id`, then caches the `BackendHandle` for reuse.** It uses the `closed` promise to detect disconnections and calls `close()` during cleanup. Your implementation must ensure `closed` resolves when the transport fails and `close()` releases all resources.

### Can a custom backend support both `exec` calls and callable functions?

**Yes, set `callable: true` to enable structured input/output.** When `callable` is true, callers can invoke `ws.runtime.exec()` with an `input` object and receive a typed `result`. The RPC stub handles both modes transparently.

### What synchronization mode should I use for a backend with its own filesystem?

**Use `sync: "remote"` (the default).** This tells the Workspace to use `push()` and `pull()` to synchronize SQLite state. Use `sync: "none"` only if your backend operates directly on the host's storage, such as an in-process mock with shared memory.