Replace the Sandbox class
Last updated Oct 23, 2026
Replace the 0.12 Sandbox class with a Durable Object class that you write. In 0.12, your Worker calls package methods through getSandbox(). In 1.0, your class starts the container through this.ctx.container, and your Worker calls the methods that your class defines.
Before you start
Your application runs @cloudflare/sandbox 0.12.14. This page replaces the Sandbox class, whether you export it from the package or extend it. It also replaces getSandbox() and its options, class fields such as sleepAfter and envVars, and the hooks onStart(), onStop(), onError(), and onActivityExpired().
The page ends with the Worker running under wrangler dev. You cannot undo the deploy that switches your sandboxes to 1.0. Before you deploy, follow Plan the move to Sandbox SDK 1.0.
The steps keep your class name, which moves every sandbox in place. To move sandboxes side by side, write the class under a new name, such as SandboxV1, and add its Wrangler configuration as Move sandboxes side by side describes.
Replace the class
- Install version 1 of the package:npmyarnpnpmbun
npm i @cloudflare/sandbox
yarn add @cloudflare/sandbox
pnpm add @cloudflare/sandbox
bun add @cloudflare/sandbox
- Replace the
FROM docker.io/cloudflare/sandboxline in yourDockerfilewith a base image that has the tools your commands use, and copy thesandbox-shimhelper into it:
Dockerfiledockerfile
```dockerfile FROM node:24-trixie-slim
RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates git python3 \ && rm -rf /var/lib/apt/lists/*
COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim
WORKDIR /workspace CMD ["sleep", "infinity"] ```
The 0.12 image included Python, Node.js, and Git. Keep the ones your commands use. Files, S3Mount, and DirectoryBackup need sandbox-shim. Match its tag to the @cloudflare/sandbox version that you installed. Remove EXPOSE lines, which 1.0 does not use. Commands that exec() runs do not see ENV lines, so pass those values in the env option instead.
3. Change the containers entry for your class in the Wrangler configuration. Keep your class name and its binding, so each name reaches the Durable Object and storage it reached in 0.12. Replace my-worker-mysandbox-v2 with a name that your account does not use yet:
jsonc
{
"containers": [
{
"class_name": "MySandbox",
"name": "my-worker-mysandbox-v2",
"scheduling_policy": "durable_object",
"images": {
"sandbox": {
"dockerfile": "./Dockerfile",
},
},
},
],
}
```toml [[containers]] class_name = "MySandbox" name = "my-worker-mysandbox-v2" scheduling_policy = "durable_object"
[containers.images.sandbox] dockerfile = "./Dockerfile" ```
The new entry makes these changes:
- scheduling_policy: "durable_object" lets each Durable Object start its own container. This policy does not accept image, instance_type, or max_instances, so remove them. The Dockerfile moves to images, and the instance type moves to start().
- The container application gets a new name. Without one, the new application takes the name that Wrangler generated for the old application, and the deploy fails after the new Worker version is already live.
- Leave durable_objects and your migrations or exports as they are. The class keeps its name, so it needs no class migration.
Also remove SANDBOX_TRANSPORT, SANDBOX_INSTANCE_TIMEOUT_MS, SANDBOX_PORT_TIMEOUT_MS, and SANDBOX_POLL_INTERVAL_MS from vars, and keep nodejs_compat, which 1.0 requires.
4. Check your compatibility date.
Note
this.ctx.exports is undefined before compatibility date 2025-11-17, unless you add the enable_ctx_exports compatibility flag. The migration pages for outbound rules and bucket mounts use it. Several 0.12 templates set 2025-05-06.
5. Generate types for the new configuration:npmyarnpnpm
npx wrangler types
yarn wrangler types
pnpm wrangler types
- Write the class. In this example, the 0.12 subclass sets a lifetime and environment variables, and overrides
onStart():
src/index.ts (0.12)ts
```ts import { getSandbox, Sandbox } from "@cloudflare/sandbox";
export class MySandbox extends Sandbox
override async onStart() {
await this.ctx.storage.put("startedAt", Date.now());
}
} ```
In 1.0, the class extends DurableObject and starts the container itself:
src/index.ts (1.0)ts
```ts import { DurableObject } from "cloudflare:workers";
// Replaces sleepAfter and envVars. const INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000; const ENV = { NODE_ENV: "test" };
export class MySandbox extends DurableObject
private get container(): Container {
const container = this.ctx.container;
if (!container) {
throw new Error("No container is configured");
}
return container;
}
private setup: Promise<void> | null = null;
private ensureRunning(): Promise<void> {
// Set up each new container, and a running container
// after this Durable Object restarts.
if (this.setup === null || !this.container.running) {
this.setup = this.startContainer().catch((error) => {
this.setup = null;
throw error;
});
}
return this.setup;
}
private async startContainer(): Promise<void> {
const newContainer = !this.container.running;
if (newContainer) {
this.container.start({
image: this.container.images.sandbox,
instance: "standard-1",
env: ENV,
// 0.12 allowed Internet access by default.
enableInternet: true,
});
}
try {
if (newContainer) {
// Replaces onStart().
await this.ctx.storage.put("startedAt", Date.now());
}
await this.container.setInactivityTimeout(
INACTIVITY_TIMEOUT_MS,
);
} catch (error) {
// The next request starts a new container.
await this.container.destroy();
throw error;
}
}
} ```
Call ensureRunning() before each use of the container. It runs startContainer() once for each new container, and once more when a restarted Durable Object finds the container running. A deploy restarts the Durable Object and can stop setup partway, so every step after start() must be safe to run again. running is true as soon as start() returns, so requests that arrive during setup wait for the same startContainer() call. If a setup step throws, startContainer() stops the container, so no request runs commands in a container that is not set up.
start() returns before the container is ready, and the first exec() waits for it. The inactivity timeout does not survive a Durable Object restart. The constructor sets it again when a restarted Durable Object finds the container running. The timeout accepts at most 6 hours. If your commands do not need the Internet, set enableInternet to false.
7. Replace each class field and getSandbox() option that your code sets:
| 0.12 | 1.0 |
|---|---|
sleepAfter |
setInactivityTimeout() after start(), and again in the constructor. |
keepAlive |
An alarm that runs more often than the inactivity timeout while work remains. |
envVars, setEnvVars() |
env on start() for the main process, and env on each exec() call. Commands do not inherit the start() variables, except PATH. |
entrypoint |
entrypoint on start(), or CMD in the image. |
enableInternet |
enableInternet on start(), which every call must pass. 0.12 defaulted to true. |
labels, setLabels() |
labels on start(). |
defaultPort, requiredPorts |
this.container.getTcpPort(port). Refer to Move preview URLs. |
allowedHosts, deniedHosts, outboundByHost, outbound, interceptHttps |
Refer to Move outbound rules. |
normalizeId |
Lowercase the name before getByName(). |
containerTimeouts |
An AbortSignal on the calls that need a deadline. Refer to Replace timeouts. |
enableDefaultSession |
Nothing. Each exec() call is its own process. Refer to Replace sessions. |
transport, pingEndpoint |
Nothing. Remove them. |
| 8. Replace the hooks your subclass overrides: | |
- onStart(): run the code in startContainer(), inside if (newContainer), as in the class from the previous step. |
|
- onStop() and onError(): watch the container with monitor(), and catch errors where you call start() and exec(). |
|
- onActivityExpired(): no code runs before the inactivity timeout stops the container. To run code first, keep your own idle deadline in an alarm, and call this.container.destroy() when it passes. For an example that saves the sandbox before it stops, refer to Save a sandbox automatically. |
To watch the container, call a method like this one in the try block of startContainer(). A restarted Durable Object runs startContainer() again, so it watches the running container too:
src/index.tsts
```ts
export class MySandbox extends DurableObject
private watch(): void {
this.container.monitor().then(
() => console.log("Exited with code 0"),
(error: unknown) => console.error("Stopped", error),
);
}
} ```
monitor() resolves when the main process exits with code 0 or destroy() stops the container without a reason. It rejects when the process fails or destroy() passes a reason. The call ends when the Durable Object leaves memory, so it misses a stop that happens before the next request. While the call keeps the Durable Object in memory, the inactivity timeout does not start, and the container keeps running past the timeout. For more information, refer to Sandbox lifetime. For a record of every stop, refer to Run code when a sandbox stops.
9. Move the calls your Worker makes into methods on the class. In 0.12, the Worker calls package methods on the stub that getSandbox() returns:
src/index.ts (0.12)ts
```ts
export default {
async fetch(request: Request, env: Env): Promise
return Response.json({
exitCode: result.exitCode,
output: result.stdout,
});
},
}; ```
In 1.0, the stub has only the methods your class defines. Add a method to MySandbox, and call it from the Worker:
src/index.ts (1.0)ts
```ts
export class MySandbox extends DurableObject
async test(): Promise<{ exitCode: number; output: string }> {
await this.ensureRunning();
const process = await this.container.exec(["npm", "test"], {
cwd: "/workspace/app",
env: ENV,
});
const result = await process.output();
return {
exitCode: result.exitCode,
output: new TextDecoder().decode(result.stdout),
};
}
} ```
src/index.ts (1.0)ts
```ts
export default {
async fetch(request: Request, env: Env): Promise
return Response.json(await sandbox.test());
},
} satisfies ExportedHandler
Move the methods that your 0.12 subclass defines into the 1.0 class. Your Worker calls them on the stub as it did in 0.12. Inside those methods, replace this.exec() and other package calls with calls on this.container. For each kind of call, refer to Change command calls and the feature pages in Migrate from Sandbox SDK 0.x.
Check the class
Start your Worker locally:
npmyarnpnpm
npx wrangler dev
yarn wrangler dev
pnpm wrangler dev
Wrangler builds the image when it starts. Send a request to each route that uses a sandbox. The first request for each name starts a container and waits for it. Each route returns what it returned on 0.12.
Next steps
- To deploy the new class, follow Plan the move to Sandbox SDK 1.0.
- To change
exec()and sessions, refer to Change command calls. - To change file calls, refer to Change file calls.
- For the methods on
this.ctx.container, refer to the Container API.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/sdk/migrate/replace-the-sandbox-class/#page","headline":"Replace the Sandbox class","description":"Replace the Sandbox class from @cloudflare/sandbox 0.12, or your subclass of it, with a Durable Object class that starts its own container.","url":"https://developers.cloudflare.com/sandbox/sdk/migrate/replace-the-sandbox-class/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/sdk/migrate/replace-the-sandbox-class/og.png?v=5defa52ddfaf0224","dateModified":"2026-10-23","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Replace @cloudflare/sandbox 0.12 terminal(), session.terminal(), proxyTerminal(), and SandboxAddon with tmux sessions that your Durable Object runs. title: Move browser terminals image: https://developers.cloudflare.com/sandbox/sdk/migrate/terminals/og.png?v=ede3fefcaf00d10b
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
Docs