Skip to main content

Guide

One package, many places to run code. This page is the one to read before the rest: what Code Sandboxes actually gives you over calling a backend's own SDK, what each backend still decides for you, and how to choose between them.

It used to weigh Code Sandboxes against E2B and Modal as alternatives. They are not alternatives any more: both are backends this package drives, alongside Daytona, CoreWeave and Cloudflare. variant="e2b" and variant="modal" run code on E2B and on Modal, through their own SDKs, with their own accounts and their own bills.

So the question this page answers is not which of them to use instead. It is what the package adds over calling one of them directly, and what each of them still decides for you once you are on it.

What Code Sandboxes Adds​

  • One result, whichever backend answered. run_code comes back as an ExecutionResult everywhere, with the failure split three ways: the sandbox (execution_ok), the process (exit_code), and the code itself (code_error). A caller that handles those handles every backend.
  • One vocabulary for the rest. sandbox.files, sandbox.commands, create_context(), set_timeout(), names and tags mean the same thing on each backend and are mapped onto whatever it actually offers — a filesystem API where there is one, a snippet that decodes base64 where there is not.
  • CRUD in one place. code-sandboxes list, get, create and delete work across the backends that can answer them; see the management guide.
  • A refusal instead of a surprise. Where a backend cannot do what was asked, the call says so rather than quietly doing something else: network_policy="allowlist" on E2B is refused because E2B has no host allowlist, list on Cloudflare raises because the bridge cannot enumerate sandboxes, and a value that cannot cross as JSON is refused rather than mangled.
  • Nothing is required to be a cloud. eval, monty, docker and jupyter-server run on the machine in front of you, through the same interface as the cloud backends — which is what makes a test suite that exercises the interface cheap to run.

The package is BSD-3 licensed and no backend is privileged in it. Moving between them is a change of one string.

The Same Program, On Any Backend​

from code_sandboxes import Sandbox

for variant in ("eval", "e2b", "modal", "daytona", "coreweave"):
with Sandbox.create(variant=variant) as sandbox:
print(variant, sandbox.run_code("40 + 2").text) # "42" on each

What the interface cannot do is give a backend a capability it does not have. That is the other half of the comparison, and it is the table below.

What Each Backend Brings​

BackendRuns code inState between callsRich display dataGPUManagement verbs
cloudflarea container on the edge, through a bridge Workernone — a process per snippetnonot exposed herecreate, get, delete
coreweavea container on CoreWeavea session process, falling back to nonenogpu=create, list, get, delete
datalayera Datalayer runtimea kernelyesgpu=create, list, get, update, delete
daytonaa Daytona sandboxthe code interpreter, one namespace per contextnogpu=, spot capacitycreate, list, get, update, delete
e2ba Firecracker microVMa Jupyter kernel per contextyesnot exposed herecreate, list, get, delete
modala Modal containera session process, falling back to nonenogpu=create, list, get, update, delete

"Not exposed here" is about this package, not about the vendor: it means the variant has no gpu= to pass on, not that the platform has no hardware.

The local backends answer the same interface with none of the accounts: docker runs a container on this machine, jupyter-server a kernel of a Jupyter Server, monty a restricted Python subset in this process, and eval plain exec() — which isolates nothing and is for development and tests.

What The Package Calls On Your Behalf​

The SDKs underneath are still there, and this is roughly what a variant does with them. The point of the table is not that one column is better: it is that the left column is the same sentence in every row, and the others are not.

OperationCode SandboxesE2B SDKModal SDK
Create a sandboxSandbox.create(variant=...)Sandbox.create()modal.Sandbox.create(app=app, image=image)
Run a snippetsandbox.run_code(code)sandbox.run_code(code)sandbox.exec("python", "-c", code)
Stream outputon_stdout=callbackon_stdout=callbackfor line in process.stdout
A value back from the coderesult.text, result.resultsexecution.results— read it off stdout yourself
Shell commandsandbox.commands.run(cmd)sandbox.commands.run(cmd)sandbox.exec(*args)
Write a filesandbox.files.write(path, content)sandbox.files.write(path, content)sandbox.open(path, "w")
Read a filesandbox.files.read(path)sandbox.files.read(path)sandbox.open(path)
Extend the lifesandbox.set_timeout(seconds)sandbox.set_timeout(seconds)—
Terminatesandbox.terminate()sandbox.kill()sandbox.terminate()
List what is runningget_manager(variant).list()Sandbox.list()modal.Sandbox.list()
A GPUSandbox.create(variant="modal", gpu="T4")—modal.Sandbox.create(gpu="T4")

Where a backend has nothing for a row, the variant either builds it — Cloudflare and CoreWeave run each snippet through a small program that reports the trailing expression and the traceback, because exec on its own reports neither — or refuses it, and its page says which.

Coming from Daytona​

# Daytona, directly
from daytona import CreateSandboxFromSnapshotParams, Daytona

client = Daytona()
sandbox = client.create(CreateSandboxFromSnapshotParams(labels={"app": "mine"}))
reply = sandbox.code_interpreter.run_code("print('hello')")
print(reply.result)
sandbox.delete()

# Through Code Sandboxes
from code_sandboxes import Sandbox

with Sandbox.create(variant="daytona") as sandbox:
result = sandbox.run_code("print('hello')")
print(result.stdout)

Both go through Daytona's CODE INTERPRETER rather than process.code_run, so the namespace survives between calls either way. Credentials are Daytona's own — DAYTONA_API_KEY, or DAYTONA_JWT_TOKEN with DAYTONA_ORGANIZATION_ID — and DAYTONA_API_URL and DAYTONA_TARGET still choose the API and the region.

Two things the variant adds beyond the shared interface. The value of a trailing expression is reported: Daytona's interpreter answers with what the code PRINTED and drops the value of a last expression, so 1 + 1 returns nothing there and "2" here. And a GPU is asked for by argument rather than by assembling an image — gpu="H100", or gpu="H100,H200" for the fallback order Daytona walks when the first is unavailable, with spot=True for preemptible capacity and preempted_at() to find out whether it was reclaimed. See Daytona.

Coming from E2B​

The sandbox is the same sandbox; what changes is the interface in front of it, and that a second backend costs one string.

# E2B, directly
from e2b_code_interpreter import Sandbox

sandbox = Sandbox.create()
execution = sandbox.run_code("print('hello')")
sandbox.kill()

# Through Code Sandboxes
from code_sandboxes import Sandbox

with Sandbox.create(variant="e2b") as sandbox:
result = sandbox.run_code("print('hello')")
print(result.stdout)

E2B_API_KEY is read the same way in both, and E2B_DOMAIN still points at a self-hosted cluster. What the variant adds is the shared result model, the management verbs, and the refusal where E2B's own model has no answer — see E2B.

Coming from Modal​

# Modal, directly
import modal

app = modal.App.lookup("my-app", create_if_missing=True)
sandbox = modal.Sandbox.create(app=app, gpu="T4")
process = sandbox.exec("python", "-c", "print('hello')")
print(process.stdout.read())
sandbox.terminate()

# Through Code Sandboxes
from code_sandboxes import Sandbox

with Sandbox.create(variant="modal", gpu="T4") as sandbox:
result = sandbox.run_code("print('hello')")
print(result.stdout)

Credentials are Modal's own — modal token new, or MODAL_TOKEN_ID with MODAL_TOKEN_SECRET. See Modal.

Choosing a Backend​

Use caseBackend
Development and tests, no account, no isolationeval
LLM-generated snippets that must not touch the hostmonty
Notebook-shaped work with a kernel, locallyjupyter-server, docker
An agent session that keeps its variables, in a cloude2b, daytona, datalayer
Figures and HTML back from the codee2b, datalayer, jupyter-server
ML training on a GPUcoreweave, modal, daytona, datalayer
The cheapest GPU capacity, preemption accepteddaytona with spot=True
Short snippets close to the user, no state to keepcloudflare
Production runtimes with snapshots and quotasdatalayer

What This Package Is Not​

It is not a compute platform. Capacity, regions, quotas, cold starts and the bill belong to the backend you chose, and no interface in front of them changes any of it. It adds no isolation either: an eval sandbox is exec() in this process however carefully the result is reported, and a sandbox is only as contained as the backend running it.

What it does is make the choice reversible.