How to Integrate Cloudflare Workspace with Durable Objects: Complete Integration and Migration Guide
Use the @cloudflare/computer SDK to instantiate a Workspace inside your Durable Object, which exposes fs, runtime, and sync APIs over Capnweb RPC to a containerized process.
A Cloudflare Workspace provides a virtual filesystem backed by SQLite inside a Durable Object (DO), enabling stateful file operations and command execution that persist across requests. This guide walks through the integration pattern used in the cloudflare/computer repository and explains the migration strategies required when upgrading versions.
Architecture Overview
The Workspace architecture consists of three layers that communicate through a typed RPC protocol.
| Component | Role |
|---|---|
| Durable Object | Owns SQLite-backed storage via packages/dofs and exposes a Workspace facade |
| Capnweb RPC | Wire format defined in packages/rpc/src/interface.ts with SyncRPC for replication and ShellRPC for command execution |
| computerd | Container process mounting the workspace via FUSE, executing user code |
The RPC surface splits into two halves as defined in packages/rpc/src/interface.ts:
export interface WorkspaceRPC {
sync: SyncRPC; // push/fetch change entries, object transfer
shell: ShellRPC; // spawn, query, kill and dispose exec handles
}
Sync flow: The DO pushes local changes via push while the container pulls remote changes through fetchChanges. Object payloads stream as ReadableStream<{hash,bytes}> to bound memory usage.
Shell flow: exec returns a streaming handle (ReadableStream<ExecEvent>) that survives reconnections through getExec, enabling long-running commands to persist across network glitches.
Binding a Workspace to a Durable Object
Instantiate the Workspace class inside your DO constructor or method. The binding name must match your wrangler.toml configuration.
import { Workspace } from "@cloudflare/computer";
export class MyDO extends DurableObject {
workspace = new Workspace({
binding: "Agent", // matches wrangler.toml binding name
id: self.ctx.id.toString(), // DO's unique identifier
});
async hello() {
await this.workspace.fs.writeFile("/workspace/hello.txt", "Hello, DO!\n");
const run = await this.workspace.runtime.exec("cat /workspace/hello.txt", {
encoding: "utf8",
});
return run.stdout; // → "Hello, DO!\n"
}
}
Key implementation details from packages/computer/src/:
- The
Workspaceconstructor buildscreateWorkspaceClient, which constructs the Capnweb stub before WebSocket upgrade completes—calls queue until the socket is ready - All paths are absolute and rooted at
/workspace; the DO enforces bounds checking (out-of-bounds paths return 400) - See
examples/worker-shell/README.mdfor a complete runnable demonstration
File System Operations
The workspace.fs API provides standard filesystem methods backed by the DO's SQLite VFS.
// Create directory structure
await this.workspace.fs.mkdir("/workspace/logs", { recursive: true });
// Write file
await this.workspace.fs.writeFile("/workspace/logs/today.txt", "Log entry\n");
// Read with encoding
const text = await this.workspace.fs.readFile("/workspace/logs/today.txt", "utf8");
// List directory contents
const entries = await this.workspace.fs.readdir("/workspace");
Command Execution
Use workspace.runtime.exec to run commands in the container environment.
const exec = await this.workspace.runtime.exec(
"bash -c 'echo $PWD && ls /workspace'",
{
cwd: "/workspace",
encoding: "utf8",
}
);
console.log(exec.stdout);
Handling Exec Restarts After Reconnection
Exec handles are durable. Store the execution ID to resume after DO restarts:
// Start long-running command
const { id, events } = await this.workspace.runtime.exec("sleep 30 && echo done");
// Persist ID for recovery
await env.KV.put("my-long-run-id", id);
// After restart, re-attach to existing execution
const { events: resumed } = await this.workspace.runtime.getExec({ id });
for await (const ev of resumed) {
if (ev.name === "stdout") {
console.log(new TextDecoder().decode(ev.value));
}
}
The resumption capability lives in packages/computerd/src/exec/log.ts, which maintains a durable SQLite event log.
Migration Strategies
The cloudflare/computer repository provides structured approaches for evolving Workspaces without data loss, documented in docs/18_runtime_migration.md.
Compatibility Date Management
Each DO declares a compatibilityDate via constructor option or environment variable. The server reports its supported date range during Capnweb handshake—mismatches reject connections, forcing explicit upgrades.
new Workspace({
binding: "Agent",
id: self.ctx.id.toString(),
compatibilityDate: "2024-08-01", // Pin production dates
});
Schema Upgrades
The VFS schema in packages/dofs/src/sync/schema.ts is versioned. Migration logic in packages/computer/src/migration.ts runs one-time transformations:
- Detect current schema version from SQLite metadata
- Apply sequential migrations to reach target version
- Guard with migration-status flag in DO storage to ensure idempotency
RPC Version Bumps
Breaking changes to WorkspaceRPC require new stub versions. The client passes its version string during createWorkspaceClient; server validation during handshake forces redeployment on mismatch.
Graceful Reconnection and Data Continuity
Three mechanisms ensure migration safety:
- Durable exec handles: Survive server restarts via
getExecreattachment - Automatic stub recreation: DO re-establishes RPC after migration
- Content-addressed sync: Change entries use
sha256(chunk)hashing;hasObjects/pushObjectsflows inpackages/dofs/src/sync/changes.tsre-push missing chunks post-migration
Migration-Safe Sync Push
// Stream changes from local modifications
await this.workspace.sync.push({
senderRev: this.currentRev, // >0 for peer DO scenarios
changes: localChangeStream,
});
// Container automatically handles hasObjects/pushObjects for missing chunks
Testing Migrations
Validate upgrades using the packages/computerd test harness:
npm run test --workspace @cloudflare/computerd
This verifies the full push/fetch cycle and streaming correctness after schema changes.
Summary
- Integration: Import
Workspacefrom@cloudflare/computer, bind to your DO with matchingwrangler.tomlconfiguration, and usefs/runtime/syncAPIs - Architecture: Capnweb RPC over WebSocket or HTTP-batch transports splits into
SyncRPCfor data replication andShellRPCfor command execution - Path enforcement: All filesystem operations use
/workspaceroot with server-side bounds checking - Migration safety: Pin
compatibilityDate, implement idempotent schema migrations inpackages/computer/src/migration.ts, and leverage durable exec handles for continuity - Data integrity: Content-addressed change entries enable automatic chunk re-synchronization across versions
Frequently Asked Questions
What happens if I don't specify a compatibility date?
The connection may succeed initially but fail unpredictably when the server updates. Without an explicit compatibilityDate, your DO cannot detect version mismatches during the Capnweb handshake, leading to potential runtime errors instead of clean rejection with upgrade guidance.
Can I run multiple Workspaces inside one Durable Object?
Yes. Instantiate multiple Workspace instances with different binding names or IDs. Each maintains independent state, RPC connections, and container processes. However, each binding consumes additional resources and WebSocket connections.
How do I recover from a failed schema migration?
Check the migration-status flag in your DO's storage. If set but incomplete, inspect packages/computer/src/migration.ts logs, fix the transformation logic, and redeploy. The idempotent design allows safe retry—migrations skip completed steps based on the stored version marker.
What triggers a full re-sync versus incremental sync?
push sends only ChangeEntry records since senderRev. If the container reports missing objects via hasObjects, the DO streams required chunks through pushObjects. A full re-sync occurs only when revision histories diverge irreconcilably, which the sync protocol detects and surfaces as an error requiring manual intervention.
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 →