# Cloudflare Sandbox SDK: Running Untrusted Code in Containers Behind a Worker

> The Sandbox SDK puts an isolated container behind a Cloudflare Worker, so a fetch handler can execute Python, read files and expose ports. It is aimed at agent and code-interpreter workloads, and its main constraint is that you need Docker locally and a Cloudflare account to deploy.

**cloudflare/sandbox-sdk** — Run sandboxed code environments on Cloudflare's edge network

- Repository: https://github.com/cloudflare/sandbox-sdk
- Website: https://sandbox.cloudflare.com/
- Stars: 1,143 · Forks: 116
- Language: TypeScript
- License: NOASSERTION
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/cloudflare-sandbox-sdk

## What the Sandbox SDK actually solves for a Worker developer

A Cloudflare Worker is not a place to run arbitrary code. It executes in an isolate with no filesystem, no process spawning and no persistent shell. The Sandbox SDK fills that gap by pairing a Worker with a container: the Worker handles routing and identity, the container handles execution. The README frames the target audience directly, listing AI code execution, interactive development environments, data analysis platforms and CI/CD systems among the intended uses.

The unit of work is a named sandbox. You call getSandbox(env.Sandbox, 'my-sandbox'), which returns a handle backed by a Durable Object, and then call methods on it: exec, writeFile, readFile. Because the handle is addressed by name, two requests that pass the same name reach the same container state, which is what makes a multi-step agent session possible at all. A code interpreter that writes a file in one request and reads it in the next only works if something keeps that file alive between requests. That something is the Durable Object plus the container behind it.

The project is not a general-purpose container platform. It is a TypeScript SDK with a specific deployment story, and the examples directory shows how narrow that story is meant to be: minimal, code-interpreter, claude-code, codex, openai-agents, websocket-tunnel, git-repo-per-sandbox. Those names describe agent and tool-integration workloads, not long-running services.

## Sandbox lifetime, labels and the Durable Object behind getSandbox

getSandbox takes a third argument for configuration. The README shows sleepAfter and labels:

```ts
const sandbox = getSandbox(env.Sandbox, 'tenant-workspace', {
  sleepAfter: '30m',
  labels: {
    tenantId: 'tenant_123',
    workload: 'code-workspace'
  }
});
```

sleepAfter is the lifetime control. It is the only lever the README documents for keeping a container from living forever, and it is string-valued ('30m'), not a number of milliseconds. If you are building anything where a sandbox maps to a tenant, this is the setting that decides how long a tenant's container stays warm after the last request.

The labels are less useful than they first appear. The README is explicit: labels are attached to the underlying Cloudflare Container for analytics and observability, and they are applied when the container starts. If you change labels while a container is already running, the new values apply the next time the container starts. That means a label is not a live tag you can update to reflect current state. It is closer to a startup annotation. If your observability plan assumes you can relabel a running sandbox to mark it as, say, failed or draining, the documented behaviour does not support that.

The Durable Object binding is declared as env.Sandbox with type DurableObjectNamespace<Sandbox>, and the Worker must re-export the class: export { Sandbox } from '@cloudflare/sandbox'. Both pieces are required. Forgetting the re-export is the kind of error that shows up as a binding failure rather than a type error, since the type declaration alone does not register the class.

## Installing the Sandbox SDK and running the minimal example locally

The README's prerequisites are Node.js 16.17.0 or later, Docker running locally, and a Cloudflare account for production deployment. Docker is not optional for local work, because the first npm run dev builds the container image.

Scaffold from the minimal template and enter the directory:

```bash
npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/examples/minimal
cd my-sandbox
```

Start the dev server:

```bash
npm run dev
```

The README notes that the first run builds the Docker container and takes 2-3 minutes, with subsequent runs much faster. Expect the first invocation to sit there; that is documented behaviour, not a hang.

Once the server is up, the template exposes two endpoints on port 8787. Hit them with curl:

```bash
# Execute Python code
curl http://localhost:8787/run

# File operations
curl http://localhost:8787/file
```

/run executes python3 -c "print(2 + 2)" through sandbox.exec and returns JSON with output and success. /file writes /workspace/hello.txt and reads it back, returning the content. If you get a response that says "Try /run or /file", you have hit a path the template does not handle.

Deploying is a single Wrangler command:

```bash
npx wrangler deploy
```

After the first deployment the README says to wait 2-3 minutes before making requests, while provisioning completes. This is a real operational constraint for automated pipelines: a deploy step that immediately probes the endpoint will report failures that are not failures.

## Quick tunnels and where the trycloudflare URL model breaks down

sandbox.tunnels.get(port) exposes a service running inside the sandbox on a *.trycloudflare.com hostname, with no Cloudflare account or DNS configuration required. The mechanism is cloudflared opening a persistent QUIC connection to Cloudflare's edge, which hands back a hostname. get() is idempotent: it checks a per-sandbox cache in Durable Object storage, returns the cached record on a hit, and only spawns a new cloudflared process on a miss. list() returns every cached tunnel, and destroy() accepts either the port number or the record.

The limitations are the interesting part, and the README states them plainly. First, quick tunnels require the RPC transport. The route-based transport's tunnels stub throws "RPC transport required". If you built on the route-based transport, this feature is not available to you without switching.

Second, URLs do not survive a container restart. Cloudflare assigns the hostname during cloudflared's startup handshake, so every restart produces a new URL. The SDK clears its cache on container start, so the next get(port) after a restart returns a fresh record. Any link you handed to a user, wrote into a database, or embedded in an email is dead after a restart. For a short-lived preview inside a single session this is fine. For anything a human bookmarks, it is not.

Third, the first fetch through a brand-new URL can take a couple of seconds while DNS propagates, even after get() has resolved. Fourth, *.trycloudflare.com buffers text/event-stream responses, so server-sent events will not stream through a tunnel; WebSockets work. If your in-sandbox service is an SSE endpoint, the tunnel is the wrong transport. Finally, local builds behind a TLS-intercepting proxy such as Cloudflare WARP need the host CA bundle injected at build time, which the README points to DOCKER_README.md for.

## Where the Sandbox SDK is the wrong choice

The clearest mismatch is transport. If you need tunnels and you are on the route-based transport, you cannot use them; the stub throws. There is no documented fallback that preserves the same API shape.

The second mismatch is URL stability. Anything that treats a sandbox preview URL as a durable address will break on restart, because the hostname is assigned at cloudflared startup and the cache is cleared on container start. If your product's model is "here is the link to your running environment," the README's documented behaviour does not support that model across restarts.

The third is streaming. text/event-stream is buffered through *.trycloudflare.com. A service that pushes progress over SSE will appear to deliver nothing until the response completes. WebSockets are the documented working path.

The fourth is local development. Docker must be running, and the first build takes 2-3 minutes. On a machine without Docker, or in a CI runner that cannot build container images, the local loop simply does not start. The README gives no alternative path for that case.

A fifth, softer constraint: the repository's licence field reads NOASSERTION, meaning GitHub could not classify it automatically. The repository contains a LICENSE file, but the automated classification did not resolve it. Anyone who needs a known licence identifier before adoption has to read that file rather than trust the metadata.

## How this differs from a general-purpose container sandbox

The obvious comparison is a container sandbox you run yourself, or a hosted one that gives you a container with an HTTP API in front of it. The difference is not the container. It is where the orchestration lives.

With a self-managed container sandbox, you run the orchestrator: something accepts a request, schedules a container, tracks its lifetime, routes traffic to it, and tears it down. You own the scheduler and the failure modes of the scheduler. With the Sandbox SDK, the Worker is the entry point and the Durable Object is the identity layer. getSandbox(env.Sandbox, 'my-sandbox') is the scheduling call. sleepAfter is the lifetime policy. You write a fetch handler, not a control plane.

That trade cuts both ways. You get request routing, named-sandbox identity and container lifetime handled by the platform, plus preview URLs without DNS setup. You give up control over things the README treats as fixed: labels only apply at container start, tunnel hostnames are assigned by Cloudflare's handshake, and the transport you choose determines whether tunnels exist at all. A self-managed sandbox lets you pin a hostname, relabel a running container, or swap the ingress layer. This SDK does not document those options.

The repository also ships a devin/ directory, an openai/ directory and an opencode.json alongside examples for claude-code, codex and openai-agents. That layout suggests the project is being shaped around coding-agent integrations specifically, which is a narrower bet than a general compute platform.

## Maintenance, releases and what upgrading costs you

The last push to the default branch was on 2026-09-10, and the most recent release listed is @cloudflare/sandbox@0.12.9 on 2026-08-27, preceded by 0.12.8 on 2026-08-24 and 0.12.7 on 2026-08-14. That is a cadence of patch releases roughly every one to two weeks across August, with the repository itself touched more recently than the last published version.

The version numbers matter more than the cadence. This is a 0.x package. The project maintains a .changeset/ directory and a changeset script in package.json, which is the standard mechanism for recording and publishing version bumps in a monorepo. It tells you releases are deliberate, not incidental. It does not tell you the API is stable. A 0.x line can and will change method signatures, and the README's own feature set has grown recently: quick tunnels appear alongside older features like command execution and file access, and the tunnel notes read like documentation written to answer questions that came up after release.

Upgrade cost is therefore mostly the cost of reading changesets before bumping. The repository's own check script runs sherif, biome check, and typechecks for the main tree plus separate e2e and perf tsconfigs, so the project holds itself to a typed, linted standard. That does not transfer to your code. If you wrap sandbox.exec, sandbox.readFile or sandbox.tunnels.get in your own abstraction, a 0.x bump can change what your wrapper has to do, and the tunnel API in particular has transport-dependent behaviour that a wrapper can hide until it breaks.

On licensing: the repository's GitHub metadata reports NOASSERTION, so the licence identifier is not machine-determined. There is a LICENSE file at the repository root. Whether that licence fits commercial use, redistribution or modification is a question for your own review of that file; nothing in the README or package metadata settles it.

## Conclusion

Adopt it if you are already on Workers and want code execution behind the same deployment, request routing and Durable Object model you use for everything else, and you accept that local development requires Docker and that production deployment needs a Cloudflare account. Do not adopt it if you need a runtime Cloudflare does not host, or if you want a sandbox that keeps the same public hostname across restarts, because the README states tunnel URLs are assigned during cloudflared's startup handshake and do not survive a container restart. Before committing, verify three things against your own account: that sandbox.tunnels.get(port) is reachable from your transport (the route-based transport throws "RPC transport required"), that your workload tolerates the 2-3 minute first container build and the documented wait after first deployment, and that the NOASSERTION licence field in the repository matches the terms your legal review needs, since GitHub could not classify it automatically.

## FAQ

### What is Cloudflare Sandbox SDK?

It is a TypeScript SDK for running untrusted code in isolated containers from a Cloudflare Worker. You get a sandbox handle via getSandbox(env.Sandbox, 'my-sandbox') and call exec, writeFile and readFile on it, backed by a Durable Object and a container.

### What is a sandbox in an API context?

In this SDK, a sandbox is a named, isolated container you address through a Worker. The README describes each sandbox as running in its own container, and getSandbox returns a handle whose methods execute commands and manage files inside it.

### What is the Cloudflare Sandbox SDK used for?

The README lists AI code execution, interactive development environments, data analysis platforms and CI/CD systems as intended uses, and the repository ships examples for code-interpreter, claude-code, codex, openai-agents and websocket-tunnel workloads.

## Sources

- [cloudflare/sandbox-sdk on GitHub](https://github.com/cloudflare/sandbox-sdk)
- [Issues](https://github.com/cloudflare/sandbox-sdk/issues)
- [Project website](https://sandbox.cloudflare.com/)
- [README](https://github.com/cloudflare/sandbox-sdk/blob/main/README.md)
- [Releases](https://github.com/cloudflare/sandbox-sdk/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cloudflare-sandbox-sdk
