Move other 0.x features

Last updated Oct 22, 2026 This page replaces or removes the Sandbox SDK 0.12 features that have no class in 1.0. Each section stands alone. Follow only the sections for the features that your application uses.

Before you start

Your 1.0 class replaces the 0.12 Sandbox class, and has the container getter, the ensureRunning() and startContainer() methods, and the ENV constant from Replace the Sandbox class. A class that you reach through this.ctx.exports needs compatibility date 2025-11-17 or later, or the enable_ctx_exports flag.

Each 0.12 feature has one section:

0.12 feature Section
Docker in a docker:dind-rootless image Run Docker inside the sandbox
outboundByHost handlers that read bindings Reach Workers bindings from the container
@cloudflare/sandbox/opencode Start an OpenCode server
@cloudflare/sandbox/openai Give OpenAI agents a shell and an editor
@cloudflare/sandbox/bridge Replace the bridge
WarmPool Remove WarmPool

Run Docker inside the sandbox

The 0.x Docker setup starts from docker:dind-rootless, switches to USER root, and copies in the musl build of the 0.x server. On a 1.0 container, its Docker daemon exits with failed to set IP forwarding '/proc/sys/net/ipv4/ip_forward' = '1': open /proc/sys/net/ipv4/ip_forward: read-only file system.

  1. Replace your Dockerfile:

Dockerfiledockerfile

   FROM docker:dind

COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim

ENTRYPOINT ["sh", "-c", "dockerd-entrypoint.sh dockerd --iptables=false --ip6tables=false --ip-forward=false > /var/log/dockerd.log 2>&1 & exec sleep infinity"]
   ```

The Docker daemon runs as `root`, with iptables and IP forwarding turned off. It logs to `/var/log`, because the `docker:dind` entrypoint mounts an empty `/tmp`. `sleep infinity` keeps the container running. Keep the `COPY` line only if your class uses `Files`, `S3Mount`, or `DirectoryBackup`. `sandbox-shim` is statically linked, so it runs on the Alpine-based `docker:dind` image.
2. Keep `enableInternet: true` in `start()`, so that Docker can pull images. 0.12 allowed Internet access by default.
3. Wait for Docker, and run containers with `--network=host`:

*src/index.ts (0.12)ts*

```ts
   const sandbox = getSandbox(env.Sandbox, "docker-sandbox");
   const run = await sandbox.exec(
    "docker run --rm --network=host alpine:3.22 " +
        "echo Hello from Docker",
   );
   ```

*src/index.ts (1.0)ts*

```ts
   export class MySandbox extends DurableObject<Env> {
    // ...

private async waitForDocker(): Promise<void> {
        const wait = await this.container.exec([
            "timeout",
            "30",
            "sh",
            "-c",
            "until docker version > /dev/null 2>&1; do sleep 0.2; done",
        ]);

if ((await wait.exitCode) !== 0) {
            throw new Error("Docker did not start within 30 seconds");
        }
    }

async runImage(): Promise<string> {
        await this.ensureRunning();
        // The Docker daemon starts after the container, so wait for it
        await this.waitForDocker();

const run = await this.container.exec(
            [
                "docker",
                "run",
                "--rm",
                "--network=host",
                "alpine:3.22",
                "echo",
                "Hello from Docker",
            ],
            { env: ENV },
        );
        const result = await run.output();
        return new TextDecoder().decode(result.stdout);
    }
   }
   ```

Without `--network=host`, `docker run` fails with `failed to disable IPv6 on container's interface eth0`. A `docker build` step that uses the network, such as a package install, also needs `docker build --network=host`. An inner container started this way shares the network access of the sandbox.
4. Call `runImage()` from your Worker. It returns `Hello from Docker`. The first call also pulls `alpine:3.22`, and Docker writes its progress to `stderr`.

For more information, refer to [Docker-in-Docker in the Containers FAQ](https://developers.cloudflare.com/containers/faq/#can-i-run-docker-inside-a-container-docker-in-docker).

## Reach Workers bindings from the container

In 0.12, an `outboundByHost` handler answered requests to a hostname such as `my.kv`, and read Workers bindings from its `env` argument. In 1.0, a `WorkerEntrypoint` answers them, and your Durable Object registers it for that hostname.

1. Replace each handler with a `WorkerEntrypoint`, and export it from the main module of your Worker:

*src/index.ts (0.12)ts*

```ts
   MySandbox.outboundByHost = {
    "my.kv": async (request: Request, env: Env) => {
        const key = new URL(request.url).pathname.slice(1);
        const value = await env.KV.get(key);
        return new Response(value ?? "", {
            status: value ? 200 : 404,
        });
    },
   };
   ```

*src/index.ts (1.0)ts*

```ts
   import { WorkerEntrypoint } from "cloudflare:workers";

type KvProps = { containerId: string };

export class KvGateway extends WorkerEntrypoint<Env, KvProps> {
    async fetch(request: Request): Promise<Response> {
        const key = new URL(request.url).pathname.slice(1);
        const value = await this.env.KV.get(key);
        return new Response(value ?? "", {
            status: value === null ? 404 : 200,
        });
    }
   }
   ```

`this.env` has the same bindings as the `env` argument. Where a 0.12 handler read `ctx.containerId`, read `this.ctx.props.containerId`.
2. Register the entrypoint in the `try` block of `startContainer()`:

*src/index.tsts*

```ts
   export class MySandbox extends DurableObject<Env> {
    // ...

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.interceptOutboundHttp(
                "my.kv",
                this.ctx.exports.KvGateway({
                    props: { containerId: this.ctx.id.toString() },
                }),
            );
            await this.container.setInactivityTimeout(
                INACTIVITY_TIMEOUT_MS,
            );
        } catch (error) {
            // The next request starts a new container.
            await this.container.destroy();
            throw error;
        }
    }
   }
   ```

`this.ctx.id.toString()` is the value that 0.12 passed as `containerId`, so keys and paths built from it stay the same. The intercept also works with `enableInternet: false`. Register one entrypoint for each hostname.

If your class also uses the `Outbound` entrypoint from [Move outbound rules](https://developers.cloudflare.com/sandbox/sdk/migrate/outbound-traffic/), add the handler to its `HANDLERS` map instead. A hostname intercept that you register after `interceptAllOutboundHttp()` receives no requests.
3. Request the hostname from the container. After you store `hello from KV` under the key `greeting`, this command prints the value:

```sh
   node -e 'fetch("http://my.kv/greeting").then(async (r) => console.log(r.status, await r.text()))'
   ```

```txt
   200 hello from KV
   ```

## Start an OpenCode server

In 0.12, `createOpencode()` started `opencode serve` in the sandbox, and returned an OpenCode SDK client that reached it through the sandbox. In 1.0, your Durable Object starts the server and creates the client, with a `fetch` function that connects to the server port.

1. Install OpenCode in your `Dockerfile`, as in [Run OpenCode in a sandbox](https://developers.cloudflare.com/sandbox/coding-agents/opencode/#switch-the-worker-to-opencode):

*Dockerfiledockerfile*

```dockerfile
   RUN npm install --global --ignore-scripts opencode-linux-x64-baseline@1.19.4 \
    && ln -s /usr/local/lib/node_modules/opencode-linux-x64-baseline/bin/opencode /usr/local/bin/opencode
   ```

The 0.12 `opencode` image included OpenCode. Start the container with the `standard-1` instance type or larger, as `startContainer()` does. On `lite`, the server did not answer requests within 30 seconds.
2. Install the OpenCode SDK in your Worker project:npmyarnpnpmbun

npm i @opencode-ai/sdk@1.19.4 ```

   yarn add @opencode-ai/sdk@1.19.4
   ```

pnpm add @opencode-ai/sdk@1.19.4 ```

bun add @opencode-ai/sdk@1.19.4

  1. Replace createOpencode() with a method that starts the server once for each container. It uses the scripts and functions from step 1 of Run a server in the background:

src/index.ts (0.12)ts

ts const sandbox = getSandbox(env.Sandbox, "my-agent"); const { client } = await createOpencode(sandbox, { directory: "/workspace", config: OPENCODE_CONFIG, }); const session = await client.session.create({ title: "Fix tests" });

src/index.ts (1.0)ts

```ts import { createOpencodeClient, type OpencodeClient, } from "@opencode-ai/sdk/v2/client";

const OPENCODE_PORT = 4096; const OPENCODE_DIR = ${SERVERS}/opencode;

// Your OpenCode configuration, without apiKey values. const OPENCODE_CONFIG = {};

const OPENCODE = [ "opencode", "serve", "--port", String(OPENCODE_PORT), "--hostname", "0.0.0.0", ]; ```

src/index.ts (1.0)ts

```ts export class MySandbox extends DurableObject { // ...

private opencodeReady = false;

private async openCode(): Promise { await this.ensureRunning();

if (!this.opencodeReady) { await startServer(this.container, OPENCODE_DIR, OPENCODE, { cwd: "/workspace", env: { ...ENV, OPENCODE_CONFIG_CONTENT: JSON.stringify(OPENCODE_CONFIG), ANTHROPIC_API_KEY: this.env.ANTHROPIC_API_KEY, }, }); await waitForServer( this.container, OPENCODE_DIR, OPENCODE_PORT, 30_000, ); this.opencodeReady = true; }

const port = this.container.getTcpPort(OPENCODE_PORT); return createOpencodeClient({ baseUrl: "http://opencode", fetch: (request) => port.fetch(request), }); }

async createSession(title: string): Promise { const client = await this.openCode(); const session = await client.session.create({ title });

if (!session.data) { throw new Error("OpenCode did not create the session"); }

return session.data.id; } } ```

Remove apiKey from your OpenCode configuration, and store each key as a secret. Like 0.12, the method passes the configuration as OPENCODE_CONFIG_CONTENT and the key in a provider variable such as ANTHROPIC_API_KEY. The container can read the key. To keep model credentials in your Worker, refer to Run OpenCode in a sandbox.

Set this.opencodeReady = false in startContainer(), inside if (newContainer), because a new container has no server. If your Durable Object restarts while the container runs, startServer() finds the server that already runs. Use the client in methods of your class, such as createSession(). 4. Serve the OpenCode web UI from the fetch() handler of your Durable Object:

src/index.tsts

```ts export class MySandbox extends DurableObject { // ...

async fetch(request: Request): Promise { const url = new URL(request.url); const accept = request.headers.get("Accept") ?? ""; const page = accept.includes("text/html") || url.pathname === "/";

// The UI calls the server at the origin in ?url=. if ( request.method === "GET" && page && !url.searchParams.has("url") ) { url.searchParams.set("url", url.origin); return Response.redirect(url.toString(), 302); }

await this.openCode(); // The container port accepts only http: URLs. url.protocol = "http:"; return this.container .getTcpPort(OPENCODE_PORT) .fetch(new Request(url, request)); } } ```

This replaces proxyToOpencode(), which made the same redirect. The UI loads its files from /, so serve it at the root of a hostname and forward every path to your Durable Object, for example with env.Sandbox.getByName("my-agent").fetch(request). The UI can run commands in the sandbox, so authenticate these requests in your Worker. 5. Call createSession("Fix tests") from your Worker. It returns the new session ID, such as ses_f1822cedcffe1CpcMlwM9eQSGZ. Open your Worker URL in a browser. It redirects to /?url= and loads OpenCode.