Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/cloudflare/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Load these when their trigger applies:
|---|---|
| [`wrangler`](https://github.com/cloudflare/skills/tree/main/skills/wrangler) | Running `wrangler` commands: deploy, dev, secrets, bindings for KV, R2, D1, Vectorize, Hyperdrive, Queues, Workflows, Containers. |
| [`cloudflare`](https://github.com/cloudflare/skills/tree/main/skills/cloudflare) | General Cloudflare platform questions outside the more specific skills above — KV, R2, D1, Vectorize, networking, security, infrastructure-as-code. |
| [`sandbox-sdk`](https://github.com/cloudflare/skills/tree/main/skills/sandbox-sdk) | Building or reviewing sandboxed-execution code paths. Relevant to [`examples/container`](../../../examples/container) and to the `computerd` container model in general. |
| [`sandbox-sdk`](https://github.com/cloudflare/skills/tree/main/skills/sandbox-sdk) | Building or reviewing sandboxed-execution code paths. Relevant to [`examples/container-legacy`](../../../examples/container-legacy) and to the `computerd` container model in general. |
| [`web-perf`](https://github.com/cloudflare/skills/tree/main/skills/web-perf) | Profiling page load, Core Web Vitals, or render-blocking issues. Rarely relevant in this repo, but listed for completeness. |
| [`cloudflare-email-service`](https://github.com/cloudflare/skills/tree/main/skills/cloudflare-email-service) | Working with Cloudflare Email Routing or the Email Workers binding. Not currently used in this repo. |

Expand Down
7 changes: 7 additions & 0 deletions .changeset/container-backend-legacy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/computer": major
"@cloudflare/dofs": minor
"@cloudflare/computer-rpc": minor
---

Rename the platform-scheduled container backend to `LegacyContainerBackend`.
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ a clean checkout: `packages/computerd` imports the sibling `@cloudflare/dofs`
and `@cloudflare/computer-rpc` packages from their `dist/`
directories, `packages/computerd`'s `src/cli/computerd.test.ts` spawns the bundled
CLI at `dist/cli/computerd.cjs`, and `examples/think-compare-runtimes`
imports `@cloudflare/computer/backends/container`, which exists only
imports `@cloudflare/computer/backends/container-legacy`, which exists only
after the `computer` package is built. Run `npm run build` across the
npm workspace before `npm test` on a clean checkout.

Expand Down Expand Up @@ -179,6 +179,6 @@ file and add it to the list above.
backed tests only run on Linux and are skipped elsewhere
automatically.
- **Examples are real consumers.** `examples/think`,
`examples/container`, and `examples/worker-shell` exercise the public
`examples/container-legacy`, and `examples/worker-shell` exercise the public
surface. If you change a public API, update them in the same
change.
2 changes: 1 addition & 1 deletion COLLABORATORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Requirements:
- Node 22 or newer. `packages/computerd` declares `"engines": { "node": ">=22" }`.
- npm. This repo uses npm workspaces, not pnpm or yarn.
- Linux with FUSE if you want to run `packages/computerd` end-to-end. The rest of the workspace builds and tests on macOS as well.
- Docker, optionally, for `examples/container`.
- Docker, optionally, for `examples/container-legacy`.

Clone and install from the repo root:

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ for setup, build, and test instructions.
The [`examples/`](examples) directory holds runnable consumers of the
public surface. Each is a Worker workspace with its own README.

- [`examples/container`](examples/container) — runs `computerd` inside a
- [`examples/container-legacy`](examples/container-legacy) — runs `computerd` inside a
container, mounts a workspace, and talks to a Durable Object over
capnweb. A `write` / `read` / `exec` HTTP surface.
- [`examples/worker-shell`](examples/worker-shell) — same HTTP surface as the
Expand Down
6 changes: 3 additions & 3 deletions docs/01_vfs.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ container, not on `WorkspaceOptions`).

```ts
import { Workspace } from "@cloudflare/computer";
import { CloudflareContainerBackend } from "@cloudflare/computer/backends/container";
import { LegacyContainerBackend } from "@cloudflare/computer/backends/container-legacy";

new Workspace({
storage: ctx.storage,
backends: [
new CloudflareContainerBackend({
new LegacyContainerBackend({
container: () => this,
workspace: { binding: "ContainerExample", id: ctx.id.toString() },
}),
Expand Down Expand Up @@ -126,7 +126,7 @@ by the in-image `FUSE_MOUNT` env var (`auto` by default; see doc 07).
On Cloudflare Containers `/dev/fuse` is exposed and the real kernel
FUSE backend mounts; under `wrangler dev` it isn't, and `auto` falls
back to the userspace shim. Either way the in-container view is a
live mirror of the DO-side VFS. Earlier revisions of `CloudflareContainerBackend`
live mirror of the DO-side VFS. Earlier revisions of `LegacyContainerBackend`
pinned `DISABLE_FUSE=1`, which produced a degraded mode where:

- The in-container filesystem at `/workspace` is the container's own
Expand Down
10 changes: 5 additions & 5 deletions docs/07_injected_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ npm run build:bin --workspace @cloudflare/computerd
# → artifacts/computerd/computerd-macos-x64
```

`examples/container/Dockerfile` is the canonical recipe for
`examples/container-legacy/Dockerfile` is the canonical recipe for
staging the binary into a container image.

## Responsibilities
Expand Down Expand Up @@ -65,7 +65,7 @@ The capnweb bootstrap interface is **`WorkspaceRPC`** (defined in

## Installing into your sandbox image

The canonical recipe is `examples/container/Dockerfile`:
The canonical recipe is `examples/container-legacy/Dockerfile`:

```dockerfile
FROM --platform=linux/amd64 debian:stable-slim
Expand Down Expand Up @@ -118,10 +118,10 @@ Provider-agnostic shape — three steps, in order:

### Cloudflare Containers specifics

`CloudflareContainerBackend` (`packages/computer/src/backends/container/cloudflare-container.ts`)
`LegacyContainerBackend` (`packages/computer/src/backends/container-legacy/cloudflare-container.ts`)
wires it like this:

1. **Start.** `WorkspaceContainerAPI.start({ env, enableInternet })`,
1. **Start.** `LegacyWorkspaceContainerAPI.start({ env, enableInternet })`,
which reaches the Cloudflare Containers API — not the
`@cloudflare/sandbox` SDK. There is no process-name registry, no
`startProcess`/`getProcess`, and no `node /app/...` command (the
Expand Down Expand Up @@ -157,7 +157,7 @@ Sharp edges actually present in `cloudflare-container.ts`:
- `#armUpgrade` must be set up *before* `#postConnect`, because `computerd`
can dial back before the `POST /connect` response returns.
- The container host records each monitored generation's exit reason. The dead container closes its WebSocket, and `fetchPort()` also short-circuits later requests with a transport error; either path invalidates the matching Workspace handle.
- **Reconnect replaces the whole session.** If the WebSocket dies, `Workspace` invalidates and closes the matching backend handle, then calls `CloudflareContainerBackend.connect()` again. The replacement runs the complete start, egress-interception, health, `/connect`, and reverse-WebSocket sequence; the backend never splices a new carrier into the dead capnweb session. Replay-safe sync and process lifecycle operations get one retry. Command spawn is retried only when no request was dispatched.
- **Reconnect replaces the whole session.** If the WebSocket dies, `Workspace` invalidates and closes the matching backend handle, then calls `LegacyContainerBackend.connect()` again. The replacement runs the complete start, egress-interception, health, `/connect`, and reverse-WebSocket sequence; the backend never splices a new carrier into the dead capnweb session. Replay-safe sync and process lifecycle operations get one retry. Command spawn is retried only when no request was dispatched.

## Environment variables

Expand Down
6 changes: 3 additions & 3 deletions docs/11_lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ lifetime policy. From the DO's perspective:
`computerd` is a long-lived process. It outlives DO restarts — the
`Container.monitor()` promise resolves only when the container itself
exits, and the backend's `#monitoring` flag drops the cached handle at
that point so the next call rebuilds from scratch (see the container host and backend implementations under `packages/computer/src/backends/container/`).
that point so the next call rebuilds from scratch (see the container host and backend implementations under `packages/computer/src/backends/container-legacy/`).

When `computerd` runs with its default in-memory store, the two sides
differ: the **container's VFS lasts only as long as the process**,
Expand Down Expand Up @@ -169,7 +169,7 @@ the `close` callback, the session is gone.
### Where capnweb attaches in our code

On the DO side: `newWebSocketRpcSession(ws)` in
`CloudflareContainerBackend.connect()` in `packages/computer/src/backends/container/cloudflare-container.ts`.
`LegacyContainerBackend.connect()` in `packages/computer/src/backends/container-legacy/cloudflare-container.ts`.
This installs `addEventListener("message", ...)` on the accepted
WebSocket, which means **the DO must be alive in memory to receive
frames**. There is no hibernation-aware variant today.
Expand Down Expand Up @@ -293,7 +293,7 @@ prove no unbounded growth under sustained workloads.

> [!NOTE]
> This section describes a target architecture, not shipped code.
> Today's `CloudflareContainerBackend` uses `server.accept()`, which
> Today's `LegacyContainerBackend` uses `server.accept()`, which
> is **not** the hibernation API. The DO stays in memory for the
> lifetime of the WebSocket. Enabling hibernation requires changes
> across capnweb and the backend; the work is sketched here so the
Expand Down
4 changes: 2 additions & 2 deletions docs/12_worker_backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { WorkerShellBackend } from "@cloudflare/computer/backends/worker-shell";

## When to reach for it

The container backend (`@cloudflare/computer/backends/container`)
The container backend (`@cloudflare/computer/backends/container-legacy`)
gives you a real Linux environment with arbitrary binaries on
`$PATH`, optional network access, and a full POSIX filesystem. It costs a
container per session and a real roundtrip on every filesystem op.
Expand Down Expand Up @@ -289,7 +289,7 @@ network-bound `git` subcommands do. See
## Example

`examples/worker-shell/` is a single wrangler project that mirrors
`examples/container/` beat for beat:
`examples/container-legacy/` beat for beat:

- One `wrangler.jsonc` with the Durable Object, an R2 mount at
`/workspace/r2`, and a `worker_loaders` binding named `LOADER`.
Expand Down
2 changes: 1 addition & 1 deletion docs/19_performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Numbers from `script/fs-bench.sh` and a full
`npm install` of [`cloudflare/sandbox-sdk`](https://github.com/cloudflare/sandbox-sdk)
(854 packages, 36,675 files), running
[`examples/container`](../examples/container) on a Cloudflare
[`examples/container-legacy`](../examples/container-legacy) on a Cloudflare
Containers **standard-2** instance (1 vCPU, 6 GiB memory, 12 GB disk).
The computerd FUSE mount lives at `/workspace`; the comparison columns are
an in-memory `tmpfs` at `/tmp` and the container's ext4 root disk at
Expand Down
16 changes: 8 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The package ships several entrypoints:
| Entrypoint | Purpose |
| --- | --- |
| `@cloudflare/computer` | The Workspace wrapper, first-class `workspace.runtime`, stub types, the R2 mount, and proxy classes. |
| `@cloudflare/computer/backends/container` | `CloudflareContainerBackend` and `withWorkspaceContainer`. Pulls in the computerd / capnweb sync plumbing. |
| `@cloudflare/computer/backends/container-legacy` | `LegacyContainerBackend` and `withLegacyWorkspaceContainer`. Pulls in the computerd / capnweb sync plumbing. |
| `@cloudflare/computer/backends/worker-shell` | `WorkerShellBackend` and the bundled just-bash command runtime. |
| `@cloudflare/computer/backends/worker-javascript` | `WorkerJavaScriptBackend`, configured libraries, durable relative imports, `node:fs/promises`, and trusted `ws:git` / `ws:artifacts`. |
| `@cloudflare/computer/git` | Opt-in isomorphic-git glue for working with checkouts inside the workspace. Bundled lazily, with `pako` replaced by Workers `node:zlib`, and kept out of the default `@cloudflare/computer` graph. |
Expand All @@ -58,7 +58,7 @@ Wire types shared with the in-container service live in the sibling package `@cl
### Sandbox container image

The container needs the `computerd` daemon alongside a FUSE runtime. The
simplest pattern, used by [`examples/container/Dockerfile`](../examples/container/Dockerfile),
simplest pattern, used by [`examples/container-legacy/Dockerfile`](../examples/container-legacy/Dockerfile),
copies the prebuilt binary out of the public GHCR image and into a thin
Debian base:

Expand Down Expand Up @@ -87,23 +87,23 @@ To build the binary from source instead, run `npm run build:bin
`artifacts/computerd/computerd-linux-x64`, then `COPY` that into the
image.

`computerd`'s own default port is `45678`; the Cloudflare container backend pins the in-image listener to `8080`, which is what `examples/container/` uses. See [07. Injected Service](./07_injected_service.md) for the env vars (`PORT`, `MOUNT_POINT`, `FUSE_MOUNT`, `EXEC_LOG_MAX_BYTES`) and the reverse-dial boot sequence.
`computerd`'s own default port is `45678`; the Cloudflare container backend pins the in-image listener to `8080`, which is what `examples/container-legacy/` uses. See [07. Injected Service](./07_injected_service.md) for the env vars (`PORT`, `MOUNT_POINT`, `FUSE_MOUNT`, `EXEC_LOG_MAX_BYTES`) and the reverse-dial boot sequence.

## Example

```ts
import { Workspace } from "@cloudflare/computer";
import {
CloudflareContainerBackend,
withWorkspaceContainer,
} from "@cloudflare/computer/backends/container";
LegacyContainerBackend,
withLegacyWorkspaceContainer,
} from "@cloudflare/computer/backends/container-legacy";
import { DurableObject } from "cloudflare:workers";

export class Agent extends withWorkspaceContainer(class extends DurableObject<Env> {}) {
export class Agent extends withLegacyWorkspaceContainer(class extends DurableObject<Env> {}) {
readonly workspace = new Workspace({
storage: this.ctx.storage, // DO storage → VFS lives here
backends: [
new CloudflareContainerBackend({
new LegacyContainerBackend({
container: () => this,
workspace: { binding: "Agent", id: this.ctx.id.toString() },
}),
Expand Down
2 changes: 1 addition & 1 deletion examples/artifacts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
},
"devDependencies": {
"typescript": "^6.0.3",
"wrangler": "^4.130.0"
"wrangler": "^4.137.0"
}
}
2 changes: 1 addition & 1 deletion examples/assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
},
"devDependencies": {
"typescript": "^6.0.3",
"wrangler": "^4.130.0"
"wrangler": "^4.137.0"
}
}
5 changes: 5 additions & 0 deletions examples/celld/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
.wrangler/
.dev.vars*
!.dev.vars.example
1 change: 0 additions & 1 deletion examples/celld/.wrangler/cache/cf.json

This file was deleted.

2 changes: 1 addition & 1 deletion examples/celld/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@
},
"devDependencies": {
"typescript": "^6.0.3",
"wrangler": "^4.130.0"
"wrangler": "^4.137.0"
}
}
File renamed without changes.
File renamed without changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Example commands retain the old workspace name

The README and Wrangler comment still select @example/computer-container. Contributors cannot run the renamed @example/computer-container-legacy workspace with those commands.

(Refers to this code)

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ client ─► Worker /c/<name>/{file,exec}
└────────── capnweb session ◄──────┘
```

1. The DO constructs a `CloudflareContainerBackend` from
`@cloudflare/computer/backends/container` and hands it to a
1. The DO constructs a `LegacyContainerBackend` from
`@cloudflare/computer/backends/container-legacy` and hands it to a
`Workspace` instance. That backend owns the entire computerd lifecycle:
container start,
outbound egress interception, port-readiness polling, POST
Expand Down Expand Up @@ -59,7 +59,7 @@ client ─► Worker /c/<name>/{file,exec}

The DO extends the plain `DurableObject` class from
`cloudflare:workers`. The container lifecycle plumbing all lives
in `CloudflareContainerBackend` — the DO is a thin host.
in `LegacyContainerBackend` — the DO is a thin host.

The container mounts computerd's VFS at `MOUNT_POINT` (`/workspace`) so
`exec`'d commands see the same tree the RPC surface reads and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@example/computer-container",
"name": "@example/computer-container-legacy",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Renamed example breaks its CI job

@example/computer-container-legacy replaces the workspace still referenced by the example matrix. The job fails before validating the example and blocks CI.

Learn more

The example was renamed both as a directory and as an npm workspace. The CI matrix still passes the old workspace name to npm run build:types, npm run typecheck, and npm test, and passes the deleted directory to Biome. npm cannot resolve @example/computer-container, so the matrix job exits before those checks run.

Example: The example/container job executes npm run build:types --workspace @example/computer-container. Only @example/computer-container-legacy exists, so npm reports no matching workspace instead of generating types.

Recommended fix: Update the container entry in the example matrix to use workspace: "@example/computer-container-legacy", path: examples/container-legacy, and a matching display name.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

"version": "0.0.0",
"private": true,
"type": "module",
Expand All @@ -17,6 +17,6 @@
},
"devDependencies": {
"typescript": "^6.0.3",
"wrangler": "^4.130.0"
"wrangler": "^4.137.0"
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Example Worker + container-enabled Durable Object that runs a
// Workspace inside a Cloudflare Container.
//
// The DO is a thin shell over CloudflareContainerBackend: it picks
// The DO is a thin shell over LegacyContainerBackend: it picks
// the container (this.ctx.container) and the egress fetcher
// (ctx.exports.WorkspaceProxy bound to this DO instance), forwards
// container-bound /api upgrades back through the backend, and
Expand All @@ -28,9 +28,9 @@ import {
withWorkspace,
} from "@cloudflare/computer";
import {
CloudflareContainerBackend,
withWorkspaceContainer,
} from "@cloudflare/computer/backends/container";
LegacyContainerBackend,
withLegacyWorkspaceContainer,
} from "@cloudflare/computer/backends/container-legacy";
import { createCloudflareObserver } from "@cloudflare/computer/observe/cloudflare";

// Re-export so the runtime can build a loopback binding for the
Expand All @@ -48,8 +48,8 @@ export { WorkspaceProxy };
// ContainerExample because withWorkspace's options callback needs it
// while constructing the Workspace: base-class fields are initialized
// by the time the callback runs, subclass fields are not.
class ContainerBase extends withWorkspaceContainer(class extends DurableObject<Env> {}) {
readonly backend = new CloudflareContainerBackend({
class ContainerBase extends withLegacyWorkspaceContainer(class extends DurableObject<Env> {}) {
readonly backend = new LegacyContainerBackend({
container: () => this,
workspace: { binding: "ContainerExample", id: this.ctx.id.toString() },
egress: { mode: "direct" },
Expand Down
2 changes: 1 addition & 1 deletion examples/egress/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
"devDependencies": {
"typescript": "^6.0.3",
"vitest": "^4.1.11",
"wrangler": "^4.130.0"
"wrangler": "^4.137.0"
}
}
Loading
Loading