Skip to content

How to run on E2B

E2B runs each sandbox in a Firecracker microVM with a live code interpreter. It is the MICROVM tier and the backend to pick for untrusted, multi-tenant code (isolation tiers).

API key

uv add "sandboxio[e2b]"
export E2B_API_KEY=e2b_...        # from the E2B dashboard; use a key scoped to one team
sandboxio doctor                  # shows "E2B_API_KEY set" — never the value

The key is read from the environment when a sandbox is created, never from a DSN and never from a dotfile. A missing key raises AuthError (SBX_E1501) naming E2B_API_KEY. Keep keys short-lived and least-privilege; sandboxio never persists them and redacts them from every log, exception, repr, audit event and span.

import sandboxio
from sandboxio_e2b import E2BConfig

sb = await sandboxio.create("e2b://")                          # default template
sb = await sandboxio.create("e2b://code-interpreter-v1?timeout=600")
sb = await sandboxio.create(E2BConfig(template="my-team-template"))

Network allowlists

E2B is the backend where NetworkPolicy(allow=...) is a real control: it maps to the provider's native deny-all-then-allow rules at create time.

import sandboxio
from sandboxio import NetworkPolicy

async with await sandboxio.create(
    "e2b://", network=NetworkPolicy(allow=("api.openai.com", "pypi.org", "files.pythonhosted.org"))
) as sb:
    res = await sb.run(["pip", "install", "-q", "httpx"], timeout=120)

egress="deny" (the default) blocks everything; egress="allow" opens everything. Hosts and CIDRs are both accepted. The policy is fixed for the sandbox's life.

Stateful code contexts and rich outputs

E2B declares STATEFUL_CODE: pass any context_id string and the adapter keeps one live interpreter per id, so variables survive between calls. A timeout or cancellation restarts that context — the only way to stop a running cell — and the next call starts clean.

import sandboxio

async with await sandboxio.create("e2b://") as sb:
    await sb.run_code("import pandas as pd; df = pd.DataFrame({'a': [1, 2, 3]})", context_id="s1")
    res = await sb.run_code("df.describe()", context_id="s1")
    for output in res.results or ():
        print(output.mime_type, output.data[:80])      # text/plain, text/html, image/png (base64)

ExecResult.results carries the interpreter's display outputs as RichOutput(mime_type, data) in Jupyter's format; binary payloads are base64 text.

Resources come from the template

CPU, memory and disk are properties of an E2B template, so Resources(cpu=..., memory_mb=...) is refused with ConfigurationError instead of being silently ignored. Build a template with the resources you need and name it in the DSN or E2BConfig.

Labels and reaping

metadata becomes sandbox metadata on E2B, plus sandboxio_managed=true and sandboxio_session=<process id>. sandboxio reap --backend e2b lists every running or paused sandbox carrying the managed flag; --kill ends them. connect() refuses a sandbox that does not carry the flag.

Behind .native

PTY, pause/resume, snapshots and the SDK's own methods are reachable as sb.native, the SDK's AsyncSandbox. Everything reached through .native is outside the semver contract: it changes when E2B changes it.