PandaStack
Migration

Migrate from E2B

A method-by-method mapping from the E2B SDK to PandaStack — sandboxes, commands, files, pause/resume, and templates — plus an honest list of what actually changes.

E2B and PandaStack solve the same problem: isolated, fast-booting sandboxes for AI agents and untrusted code, both built on Firecracker microVMs. The core concepts map almost one-to-one, so most migrations are a mechanical rename pass — the SDKs even share the sandbox.commands.run(...) spelling.

This guide is a straight mapping, not a sales pitch. Where E2B has something we don't, we say so.

Setup

E2BPandaStack
Python packagee2b / e2b-code-interpreterpandastack
JS/TS packagee2b / @e2b/code-interpreter@pandastack/sdk
API key env varE2B_API_KEYPANDASTACK_API_KEY
CLIe2b (npm)pandastack (npm, npm i -g @pandastack/sdk)
API baseE2B's APIhttps://api.pandastack.ai (Authorization: Bearer $PANDASTACK_API_KEY)
pip install pandastack           # Python SDK
npm install @pandastack/sdk      # TypeScript SDK
npm install -g @pandastack/sdk   # CLI ("pandastack" command)

export PANDASTACK_API_KEY=pds_...   # or: pandastack auth login

Cheat sheet

The full mapping, Python spellings (TypeScript is the same shape in camelCase):

E2BPandaStackNotes
Sandbox() / Sandbox.create()Sandbox.create(template="code-interpreter")Template picked at create time.
Sandbox(template="my-tpl")Sandbox.create(template="my-tpl")Same idea.
sbx.commands.run(cmd)sandbox.exec(cmd) — or sandbox.commands.run(cmd)The commands.run alias exists in both PandaStack SDKs for drop-in compatibility.
sbx.commands.run(cmd, on_stdout=...)sandbox.exec_stream(cmd, on_stdout=...)SSE-backed streaming.
sbx.run_code(code)ctx = sandbox.create_code_context() then ctx.run_code(code)Persistent kernel with rich MIME results (PNG, HTML, JSON).
sbx.files.read(path)sandbox.filesystem.read(path)Python returns bytes.
sbx.files.write(path, data)sandbox.filesystem.write(path, data)Accepts str or bytes.
sbx.files.list(dir)sandbox.filesystem.listdir(dir) (TS: filesystem.list)Returns FileInfo entries.
sbx.files.exists(path)sandbox.filesystem.exists(path)
sbx.pause()sandbox.hibernate()Memory + disk snapshotted to storage, VM stops billing RAM.
Sandbox.resume(sandbox_id)sandbox.wake() — or just send any requestHibernated sandboxes auto-wake on the next request.
Sandbox.connect(sandbox_id)Sandbox.get(sandbox_id)Reattach to a running sandbox by id.
sbx.set_timeout(seconds)sandbox.set_ttl(seconds)TTL-based lifetime.
sbx.kill()sandbox.kill()Same name.
e2b template build (e2b.toml)pandastack template build --name my-tpl -f DockerfileServer-side build from a plain Dockerfile; no config file format.
sandbox.fork() / sandbox.fork_tree(n)Copy-on-write clones of a running sandbox (memory + disk), 400–750 ms same-host.

Creating a sandbox

# E2B
from e2b_code_interpreter import Sandbox
sbx = Sandbox()

# PandaStack
from pandastack import Sandbox
sandbox = Sandbox.create(template="code-interpreter")

# Options map directly:
sandbox = Sandbox.create(
    template="code-interpreter",
    ttl_seconds=600,                  # ≈ E2B's timeout
    metadata={"run_id": "abc123"},    # same concept as E2B metadata
)

PandaStack sandboxes are also context managers — non-persistent ones are killed on exit:

with Sandbox.create(template="code-interpreter") as sandbox:
    print(sandbox.exec("echo hello").stdout)
// E2B
import { Sandbox } from "@e2b/code-interpreter";
const sbx = await Sandbox.create();

// PandaStack
import { Sandbox } from "@pandastack/sdk";
const sandbox = await Sandbox.create({ template: "code-interpreter" });

// Options map directly:
const sandbox2 = await Sandbox.create({
  template: "code-interpreter",
  ttlSeconds: 600,                    // ≈ E2B's timeoutMs
  metadata: { runId: "abc123" },
});
pandastack sandbox create --template code-interpreter --ttl 600 --metadata run_id=abc123

Create restores a pre-baked memory snapshot rather than cold-booting, at ~179 ms p50. There is no warm pool — every create takes the snapshot-restore path, so latency doesn't depend on pool hit rate.

Running commands

E2B's sbx.commands.run(...) maps to sandbox.exec(...). Both PandaStack SDKs also ship a sandbox.commands.run(...) alias with the same result shape, so this call site often needs no change beyond the import.

# E2B
result = sbx.commands.run("ls -la /home/user")
print(result.stdout, result.exit_code)

# PandaStack — either spelling works
result = sandbox.exec("ls -la /root")
result = sandbox.commands.run("ls -la /root", timeout=30)
print(result.stdout, result.exit_code)

# check=True raises CommandFailed on non-zero exit (like subprocess.run)
sandbox.exec("make test", timeout_seconds=300, check=True)

Streaming output (E2B's on_stdout / on_stderr callbacks):

exit_code = sandbox.exec_stream(
    "pip install -r requirements.txt",
    on_stdout=lambda chunk: print(chunk, end=""),
    on_stderr=lambda chunk: print(chunk, end=""),
)
// E2B
const result = await sbx.commands.run("ls -la /home/user");

// PandaStack — either spelling works
const result2 = await sandbox.exec("ls -la /root");
const result3 = await sandbox.commands.run("ls -la /root", { timeoutSeconds: 30 });
console.log(result2.stdout, result2.exitCode);

// Streaming (≈ E2B's onStdout / onStderr)
const exitCode = await sandbox.execStream("pip install -r requirements.txt", {
  onStdout: (chunk) => process.stdout.write(chunk),
  onStderr: (chunk) => process.stderr.write(chunk),
});
pandastack sandbox exec <sandbox-id> --timeout 30 -- "ls -la /root"

One behavioral difference to know about: PandaStack's exec takes a single string run under sh -c and has no separate cwd=, env=, or background= parameters. Express those in the shell instead — cd /work && ..., KEY=value cmd, setsid cmd & — or bake environment into your template. If your E2B code leans on per-command kwargs, this is the main thing you'll rewrite. See Exec — running commands.

Running code (code interpreter)

E2B's sbx.run_code(...) with rich outputs maps to a PandaStack code context — a persistent Jupyter-style kernel where variables survive across calls and charts/DataFrames come back as MIME bundles.

# E2B
execution = sbx.run_code("x = 1; x + 1")

# PandaStack
ctx = sandbox.create_code_context()          # persistent kernel
ctx.run_code("x = 41")
ex = ctx.run_code("x + 1")                   # state persists
print(ex.text)                               # "42"

ex = ctx.run_code("import matplotlib.pyplot as plt; plt.plot([1,2,3]); plt.show()")
png_base64 = ex.results[0].png               # like E2B's execution.results
print(ex.logs)                               # {"stdout": ..., "stderr": ...}

For one-off snippets without kernel state, sandbox.run_code("print(2+2)", language="python") wraps exec directly.

// E2B
const execution = await sbx.runCode("x = 1; x + 1");

// PandaStack
const ctx = await sandbox.createCodeContext();   // persistent kernel
await ctx.runCode("x = 41");
const ex = await ctx.runCode("x + 1");           // state persists
console.log(ex.text);                            // "42"

const chart = await ctx.runCode("import matplotlib.pyplot as plt; plt.plot([1,2,3]); plt.show()");
const pngBase64 = chart.results[0]?.png;         // like E2B's execution.results

The Execution shape is deliberately close to E2B's: results (MIME bundles with .png, .html, .json, .text accessors), logs (stdout/stderr), and error for tracebacks. See Code interpreter / REPL.

Files

E2B's files namespace maps to PandaStack's filesystem namespace.

# E2B
sbx.files.write("/home/user/data.csv", "a,b\n1,2")
content = sbx.files.read("/home/user/data.csv")
entries = sbx.files.list("/home/user")

# PandaStack
sandbox.filesystem.write("/root/data.csv", "a,b\n1,2")     # str or bytes
content = sandbox.filesystem.read("/root/data.csv")        # returns bytes
entries = sandbox.filesystem.listdir("/root")              # list[FileInfo]
info = sandbox.filesystem.stat("/root/data.csv")           # size, mode, mtime
ok = sandbox.filesystem.exists("/root/data.csv")

# Local ↔ sandbox transfer helpers
sandbox.filesystem.upload("./local.csv", "/root/data.csv")
sandbox.filesystem.download("/root/out.json", "./out.json")

Watch out: PandaStack's read returns bytes in Python — call .decode() where your E2B code expected str.

// E2B
await sbx.files.write("/home/user/data.csv", "a,b\n1,2");
const content = await sbx.files.read("/home/user/data.csv");

// PandaStack
await sandbox.filesystem.write("/root/data.csv", "a,b\n1,2");
const text = await sandbox.filesystem.read("/root/data.csv");   // string
const entries = await sandbox.filesystem.list("/root");         // FileInfo[]
const info = await sandbox.filesystem.stat("/root/data.csv");
const ok = await sandbox.filesystem.exists("/root/data.csv");

// Local ↔ sandbox transfer helpers
await sandbox.filesystem.upload("./local.csv", "/root/data.csv");
await sandbox.filesystem.download("/root/out.json", "./out.json");
pandastack fs write <sandbox-id> --path /root/data.csv --content "a,b"
pandastack fs read <sandbox-id> --path /root/data.csv
pandastack fs upload <sandbox-id> --local ./local.csv --remote /root/data.csv
pandastack fs download <sandbox-id> --remote /root/out.json --local ./out.json

PandaStack does not currently have an equivalent of E2B's filesystem watch API — poll with stat/listdir, or run inotifywait inside the sandbox. Details: Filesystem.

Pause/resume → hibernate/wake

E2B's pause/resume maps to PandaStack's hibernate/wake, with one nice difference: a hibernated PandaStack sandbox auto-wakes on the next request to it, so you usually don't need an explicit resume call.

# E2B (beta)
sbx.pause()
sbx = Sandbox.resume(sandbox_id)

# PandaStack
sandbox.hibernate()          # memory + disk persisted, VM stops
# ...later, from anywhere:
sandbox = Sandbox.get(sandbox_id)
sandbox.exec("echo back")    # auto-wakes transparently
# or force it without a workload request:
sandbox.wake()
// E2B (beta)
await sbx.pause();
const resumed = await Sandbox.resume(sandboxId);

// PandaStack
await sandbox.hibernate();
// ...later:
const sb = await Sandbox.get(sandboxId);
await sb.exec("echo back");    // auto-wakes transparently
// or: await sb.wake();
pandastack sandbox hibernate <sandbox-id>
pandastack sandbox wake <sandbox-id>

PandaStack also has a lighter-weight pause()/resume() pair that freezes the VM but keeps it resident in host RAM — faster to resume, but it keeps billing committed memory. Hibernate is the scale-to-zero one: RAM billing stops while asleep. There's also snapshot() (point-in-time image you can create new sandboxes from) and fork() (live copy-on-write clone, 400–750 ms same-host) — the latter has no E2B equivalent and is worth a look for tree-search agent patterns: Snapshots and forks.

Templates

Both platforms build custom sandbox images from a Dockerfile. E2B uses its CLI with an e2b.toml config; PandaStack builds server-side from a plain Dockerfile — no config file format, just a name.

# E2B
e2b template build --name my-agent-env

# PandaStack (server-side build; needs only an API key, no local Docker)
pandastack template build --name my-agent-env -f Dockerfile
pandastack template list

Then use it exactly like a first-party template:

sandbox = Sandbox.create(template="my-agent-env")

First-party templates: base, code-interpreter, agent, browser, postgres-16. Custom builds get their snapshot baked automatically, so creates from your template hit the same fast restore path. RAM is a template property (baked into the snapshot), not a per-create knob. See Templates.

What's actually different

Being honest about the deltas in both directions:

Things PandaStack has that E2B doesn't (or that work differently):

  • Copy-on-write forks of running sandboxesfork() / fork_tree(n) / explore() clone live memory + disk in 400–750 ms for branch-and-explore agent patterns.
  • Managed PostgreSQL — real Postgres 16 VMs with backups, PITR clones, and failover (POST /v1/databases, 1g/4g/16g tiers, 30–90 s create). Databases
  • Git-driven app hosting — connect a repo, get builds + blue-green deploys + a stable URL, Vercel/Render-style, with scale-to-zero (~1–2 s wake). Apps
  • Serverless functions + cron schedules — deploy a bundle, invoke over HTTP, schedule with cron. Functions
  • Self-hosting — the whole platform is open source; run it on your own AWS/GCP account or on-prem KVM hosts. Self-host
  • Pricing model — one rate card: $0.054/vCPU-hr for actively burned CPU + $0.0162/GiB-hr for committed RAM. No per-request pricing.

Things E2B has that PandaStack doesn't (today):

  • Per-command cwd/env/background kwargs and background process handles — PandaStack expresses these through the shell.
  • A filesystem watch API.
  • A desktop/GUI sandbox product line.
  • A larger ecosystem of framework integrations and a longer production track record — E2B has been at this longer, and it shows in the breadth of examples and third-party glue.

If your workload depends on any of the latter, weigh that honestly before migrating. If it's create → exec → files → kill (the vast majority of agent workloads), the migration is an afternoon.

Migration checklist

  1. pip install pandastack / npm i @pandastack/sdk; set PANDASTACK_API_KEY.
  2. Rebuild your E2B template Dockerfile with pandastack template build --name ... -f Dockerfile (drop e2b.toml).
  3. Swap imports; change Sandbox()Sandbox.create(template=...).
  4. sbx.commands.run → keep as sandbox.commands.run or rename to sandbox.exec; move any cwd=/env= kwargs into the command string.
  5. sbx.files.*sandbox.filesystem.* (listlistdir in Python; read returns bytes in Python).
  6. sbx.run_codecreate_code_context() + ctx.run_code().
  7. pause/resumehibernate/wake (or lean on auto-wake and delete the resume call).
  8. Run your suite; the REST reference covers anything the SDKs don't.

On this page