How to Implement a Custom Execution Backend in Computer Beyond Built-In Options
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 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/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 forexec,push,pull, and other operationsruntimeId?: string— optional identifier for tracingclosed: Promise<void>— resolves when the transport terminatesclose(): 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/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). The worker-shell backend ([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 usespush()/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/packages/computer/src/workspace.ts) handles file operations.
Implement Lifecycle Hooks
Your BackendHandle must provide:
- A
closedpromise that resolves when the connection drops - 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:
// 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:
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) — 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) — 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) — Full production backend withcomputerdand Cap'n-Proto. - [
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
WorkspaceBackendfrombackend.tswithid,type,sync, andconnect(). - Return a
BackendHandlecontaining aWorkspaceRPCstub,closedpromise, andclose()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]inWorkspaceconstructor. - Route execution with
{ backend: "your-id" }inws.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) 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →