Quickstart¶
Five minutes from a clean machine to code running in a sandbox that cannot reach the network. You need Python 3.11+ and, for the Docker backend, a running Docker daemon. Everything here also runs against the in-process fake, which needs neither.
1. Prove the install works¶
The demo creates a sandbox, runs code, streams output, tries to reach the network from
inside — and reports that the attempt was denied — then tears the sandbox down. The first
run pulls the python:3.12-slim image (about 130 MB, on the host, outside the sandbox's
network policy). A warm run finishes in about two seconds. If Docker is not installed, the
demo prints the exact install command and exits 1.
When something is off, ask the doctor. It prints credential variable names, never values, and makes no provider API call:
2. Add it to a project¶
From v0.1 onwards — the name currently holds a 0.0.0 placeholder with no code in it:
The base package depends on anyio and typing-extensions only. Every backend SDK lives
behind an extra and is imported the first time that backend is used.
3. Run code¶
import sandboxio
async with await sandboxio.create() as sb:
res = await sb.run_code("print('hello')")
print(res.stdout)
create() with no arguments is local Docker with the defaults every backend applies:
| Default | Value | Change it with |
|---|---|---|
| Network | egress denied | network=NetworkPolicy(egress="allow") or an allowlist on E2B |
| Timeout | 300 s for the sandbox and every call | timeout= on create(), run(), run_code(), stream() |
| Resources | modest CPU and memory caps | resources=Resources(memory_mb=2048) |
| Labels | none | metadata={"tenant_id": ..., "session_id": ...} |
timeout=None on a call means "inherit the sandbox timeout", never "unbounded";
timeout=None on create() is refused.
4. Swap the backend¶
import sandboxio
sb = await sandboxio.create("docker://python:3.12-slim")
sb = await sandboxio.create("e2b://code-interpreter-v1") # needs E2B_API_KEY
sb = await sandboxio.create("fake://") # tests; executes nothing
The DSN is <backend>://[<template>][?timeout=<seconds>] and nothing else — no secrets,
no policy. Production code SHOULD prefer typed configuration
(spec/07).
Make a deployment fail closed rather than silently downgrade:
import sandboxio
from sandboxio import IsolationTier
sb = await sandboxio.create("e2b://", require_isolation=IsolationTier.MICROVM)
If the resolved backend is weaker, create() raises ConfigurationError before anything is
provisioned. CONTAINER (Docker) is not a boundary against hostile code; read
isolation tiers before choosing.
5. Use the whole surface¶
import sandboxio
from sandboxio import Capability
async with await sandboxio.create() as sb:
await sb.files.write("/work/data.csv", "a,b\n1,2\n")
await sb.files.upload("local.txt", "/work/local.txt")
print(await sb.files.ls("/work"))
async with sb.stream(["pytest", "-q"], timeout=300) as proc: # `async with` is required
async for chunk in proc:
print(chunk.stream, chunk.data.decode(), end="")
res = await proc.wait()
if Capability.STATEFUL_CODE in sb.capabilities: # E2B and the fake, not Docker
await sb.run_code("x = 41", context_id="session-1")
print((await sb.run_code("print(x + 1)", context_id="session-1")).stdout)
sb.native # the provider's own object — outside the semver contract
Capabilities are discovered per sandbox, never inferred from the backend name. Calling
something the backend does not declare raises CapabilityNotSupported naming the backends
that do.
6. Handle errors by code¶
import sandboxio
try:
async with await sandboxio.create("e2b://") as sb:
await sb.run("sleep 999", timeout=5)
except sandboxio.SandboxTimeout as exc: # not the builtin TimeoutError
print(exc.code, exc.hint) # SBX_E1302, and the fix
except sandboxio.SandboxError as exc:
print(exc) # [code] message / Fix: ... / Docs: ...
Every exception carries a stable code, a hint holding the exact fix, and a docs URL (error reference, why codes).
7. Test without a sandbox¶
import pytest
from sandboxio import ExecResult
@pytest.mark.anyio
async def test_tool(sbx_fake):
sbx_fake.on_run_code(match="import pandas", returns=ExecResult(0, "2.2.1\n", ""))
res = await sbx_fake.sandbox.run_code("import pandas; print(pandas.__version__)")
assert "2.2.1" in res.stdout
The fixture is installed with the package. More in offline testing.
Where next¶
examples/: the same ground as complete programs you can run — one concept each, every one executed by CI.- Docker how-to: images with dependencies, offline wheelhouses, the reaper.
- E2B how-to: API key, allowlists, stateful contexts, rich outputs.
- Audit and tracing: sinks, OTel spans, redaction.
- CI: a copy-paste GitHub Actions workflow.
- Integrations: LangGraph, OpenAI Agents SDK, MCP.
- Operations:
doctor,reap, theSBX_*switches. - Coding assistant in your repo? Paste the AGENTS.md snippet.