Cloudflare Sandbox SDK: Running Untrusted Code in Durable Object Containers
Run sandboxed code environments on Cloudflare's edge network
At a glance
- What is it?
- The Sandbox SDK puts an isolated container behind a Durable Object and exposes it as exec, file and tunnel calls from a Worker. It fits AI code execution and per-tenant workspaces; it does not fit anything that needs a long-lived VM or a stable public hostname.
- Who is it for?
- Adopt it if you are already on Workers and need short-lived, isolated execution for AI agents, code interpreters or per-tenant workspaces, and you accept that each sandbox is a container that sleeps and restarts. Do not adopt it if you need a persistent VM, a stable public URL across restarts, or server-sent events through a quick tunnel.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem: executing code you did not write, from a Worker
A Worker is a good place to receive a request and a bad place to run a Python script from a stranger. The runtime is designed to isolate requests from each other, not to give one request a writable filesystem, a process table and a shell. The Sandbox SDK exists to bridge that gap. According to the README, it lets you "run untrusted code safely in isolated containers" while driving it from a Workers application, with command execution, file management, background processes and exposed services as the four surfaces.
The intended audience is narrow and identifiable. The README names AI code execution, interactive development environments, data analysis platforms and CI/CD systems. The topics list adds agent and code-interpreter, which matches the first of those. If you are building an agent that needs to run generated code, or a product where each customer gets their own scratch workspace, this is aimed at you. If you want a general-purpose compute primitive, it is not.
getSandbox, Durable Objects and where the container actually lives
The mechanism is visible in the README's quick example. You re-export the Sandbox class from your Worker entrypoint (`export { Sandbox } from '@cloudflare/sandbox'`) and declare a Durable Object namespace binding named `Sandbox` in your Env type. `getSandbox(env.Sandbox, 'my-sandbox')` then returns a handle addressed by that string id. The string is the identity: the same name gives you the same sandbox, a different name gives you a different one. That is the whole tenancy model, and it is why the README's options example uses `'tenant-workspace'` as the id.
Calls on the handle are RPC-style methods that resolve to operations inside the container: `sandbox.exec('python3 -c "print(2 + 2)"')` returns an object with `stdout` and `success`, `sandbox.writeFile` and `sandbox.readFile` take and return content. The Durable Object is the coordination point and, as the tunnels section shows, also the cache: `get()` consults "a per-sandbox cache in Durable Object storage". Container labels passed through the third argument to `getSandbox()` are attached to the underlying Cloudflare Container for analytics, and the README is explicit that they are applied at container start, so changing a label on a running container has no effect until the next start.
Lifetime is governed by `sleepAfter`, shown as `'30m'` in the options example. The README does not document what happens to in-flight work at the sleep boundary, which is a real gap if you are running long jobs.
Getting a sandbox running: the four commands that matter
The README's path is short. Scaffold with `npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/examples/minimal`, then `cd my-sandbox` and `npm run dev`. The prerequisites are Node 16.17.0 or later and a running local Docker daemon. That Docker dependency is the first thing to check, because it is not optional for local development and it is not something Workers developers usually have in their loop. The README warns that the first run builds the container and takes 2 to 3 minutes; later runs are faster.
Two endpoints are available on the dev server for a smoke test: `curl http://localhost:8787/run` for execution and `curl http://localhost:8787/file` for file operations. Production is `npx wrangler deploy`, followed by a wait. The README says to "wait for provisioning" and gives 2-3 minutes after first deployment before making requests. If you wire this into a CI pipeline that deploys and immediately probes, that wait is a failure mode you have to design around.
One config detail worth reading twice: the example calls `proxyToSandbox(request, env)` before anything else and comments it as "Required for preview URLs". Skip it and preview URLs silently stop working while the rest of the API keeps responding.
Quick tunnels: idempotent lookup, disposable hostnames
`sandbox.tunnels.get(port)` returns a record with a `url` on `*.trycloudflare.com`, with no Cloudflare account or DNS setup. The README describes cloudflared opening a persistent QUIC connection to the edge, which then hands back a hostname. Repeated calls for the same port return the same record, and `list()` returns every cached tunnel. Teardown is `destroy(8080)` or `destroy(tunnel)`.
The constraints are the interesting part. Tunnels require the RPC transport; the route-based transport's `tunnels` stub throws "RPC transport required". URLs do not survive a container restart, because the hostname is assigned during cloudflared's startup handshake. The SDK clears its cache on container start, so the next `get(port)` returns a fresh record rather than a stale one. The first fetch through a new URL can take a couple of seconds while DNS propagates even after `get()` resolves.
And there is a hard limitation that will catch anyone building a streaming UI: `*.trycloudflare.com` buffers `text/event-stream` responses. WebSockets work. If your architecture assumes server-sent events, this tunnel is the wrong transport and you need preview URLs or a WebSocket design instead.
Where the container model gets in the way
Three limitations are stated or strongly implied by the README, and they share a root cause: a sandbox is a container with a lifecycle, not a machine you own.
First, state is not durable in the way people assume. `sleepAfter: '30m'` means an idle sandbox stops. Tunnels lose their URLs on restart. Labels only apply at start. Anything you build on top has to treat the sandbox as reconstructible, which pushes persistence into your own storage layer rather than the container's filesystem.
Second, the tunnel transport is coupled to the sandbox transport. Choosing route-based transport for other reasons removes `tunnels` entirely, and the error is a runtime throw rather than a type error you would catch at build time.
Third, local development depends on Docker and, in one documented case, on TLS trust. The README notes that local builds behind a TLS-intercepting proxy such as Cloudflare WARP need the host CA bundle injected at build time, pointing at DOCKER_README.md. That is a build-environment problem, not a runtime one, but it will stop a developer cold with an error that has nothing to do with their code. If your team standardises on a corporate proxy, budget for this before you start.
The alternative: a long-lived container host you manage
The obvious comparison is running the same workload on a container platform you operate directly, such as a managed Kubernetes cluster or a plain VM with Docker, and calling into it over HTTP from your Worker. The difference in approach is not performance, it is where identity and lifetime live. With the Sandbox SDK, identity is a Durable Object name and lifetime is `sleepAfter`; the platform starts and stops the container for you, and the SDK gives you exec, file and tunnel calls over RPC without you writing an agent inside the container. With a self-managed host, you own the scheduler, the image pipeline, the health checks and the address book, and in exchange you get a process that stays up, a hostname that does not change, and no dependency on Workers bindings.
That trade is worth naming plainly: the SDK removes operational surface by giving up control over lifetime and addressing. If your workload is a ten-second code execution, that is a good trade. If your workload is a long-running service that other systems connect to by hostname, it is a bad one, and no amount of SDK convenience fixes it.
Maintenance, releases and the licence question
The package is `@cloudflare/sandbox`, and the release cadence visible in the metadata is fast: 0.12.7 on 2026-08-14, 0.12.8 on 2026-08-24, 0.12.9 on 2026-08-27. Three patch releases in under two weeks at a 0.x version. That is a normal shape for a young SDK, and it means you should pin an exact version rather than a caret range if you care about reproducible builds. The repository also ships its own development loop: `git clone`, `npm install`, `npm test`, with contribution guidance in CONTRIBUTING.md.
The licence is the item to resolve before adoption. The repository metadata reports NOASSERTION, which means GitHub could not map the licence file to a known SPDX identifier. That is not the same as "no licence", and it is not the same as any specific licence. It means the terms have to be read from the repository itself, and if your organisation requires an approved SPDX identifier in its dependency policy, this will not clear automatically. I am not giving legal advice; I am pointing at the one field in the supplied metadata that a compliance reviewer will stop on.
Editorial conclusion
Adopt it if you are already on Workers and need short-lived, isolated execution for AI agents, code interpreters or per-tenant workspaces, and you accept that each sandbox is a container that sleeps and restarts. Do not adopt it if you need a persistent VM, a stable public URL across restarts, or server-sent events through a quick tunnel. Before committing, verify three things: that Docker builds locally in your environment (including the CA bundle case behind a TLS-intercepting proxy), that your wrangler deploy path provisions the container within your first-deploy wait budget, and that your licence review covers a package whose metadata reports NOASSERTION rather than a named licence.
Community notes