# E2B Documentation Source: https://e2b.mintlify.app/docs ## What is E2B? E2B provides isolated sandboxes that let agents safely execute code, process data, and run tools. Our SDKs make it easy to start and manage these environments. Start a sandbox and run code in a few lines: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npm i e2b ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b ``` ```javascript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Needs E2B_API_KEY environment variable const result = await sandbox.commands.run('echo "Hello from E2B Sandbox!"') console.log(result.stdout) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Needs E2B_API_KEY environment variable result = sandbox.commands.run('echo "Hello from E2B Sandbox!"') print(result.stdout) ``` ## E2B building blocks A quick overview of the core building blocks you'll interact with when using E2B. * [**Sandbox**](/docs/sandbox) — A fast, secure Linux VM created on demand for your agent * [**Template**](/docs/template/quickstart) — Defines what environment a sandbox starts with ## How to use the docs The documentation is split into three main sections: * [**Quickstart**](#quickstart) — Step-by-step tutorials that walk you through creating your first E2B sandboxes. * [**Examples**](#examples) — In-depth tutorials focused on specific use cases. Pick the topics that match what you're building. * [**SDK Reference**](https://e2b.dev/docs/sdk-reference) — A complete technical reference for every SDK method, parameter, and configuration option. ## Quickstart ## Examples Build AI agents that see, understand, and control virtual Linux desktops using E2B Desktop sandboxes. Use E2B sandboxes in your GitHub Actions workflows to run testing, validation, and AI code reviews. # Amp Source: https://e2b.mintlify.app/docs/agents/amp Run Amp in a secure E2B sandbox with full filesystem, terminal, and git access. [Amp](https://ampcode.com) is a coding agent with multi-model architecture and built-in code intelligence. E2B provides a pre-built `amp` template with Amp already installed. ## CLI Create a sandbox with the [E2B CLI](/docs/cli). ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sbx create amp ``` Once inside the sandbox, start Amp. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} amp ``` ## Run headless Use `-x` for non-interactive mode and `--dangerously-allow-all` to auto-approve all tool calls. The sandbox isolates the agent from your host machine, but sandboxes can reach the open internet by default — restrict outbound traffic with [network rules](/docs/network/internet-access). Amp uses its own API key from [ampcode.com/settings](https://ampcode.com/settings). Auto-approving tool calls is contained by the sandbox: the agent cannot touch your host machine, local files, or credentials. It can still make outbound network requests — internet access is enabled by default. To limit where an auto-approved agent can connect, configure [outbound network rules](/docs/network/internet-access) (`allowInternetAccess`, plus allow/deny lists by CIDR or hostname). Hostname rules apply to HTTP(S) traffic only; use CIDR rules for other protocols. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('amp', { envs: { AMP_API_KEY: process.env.AMP_API_KEY }, }) const result = await sandbox.commands.run( `amp --dangerously-allow-all -x "Create a hello world HTTP server in Go"` ) console.log(result.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("amp", envs={ "AMP_API_KEY": os.environ["AMP_API_KEY"], }) result = sandbox.commands.run( 'amp --dangerously-allow-all -x "Create a hello world HTTP server in Go"', ) print(result.stdout) sandbox.kill() ``` ### Example: work on a cloned repository ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('amp', { envs: { AMP_API_KEY: process.env.AMP_API_KEY }, timeoutMs: 600_000, }) await sandbox.git.clone('https://github.com/your-org/your-repo.git', { path: '/home/user/repo', username: 'x-access-token', password: process.env.GITHUB_TOKEN, depth: 1, }) const result = await sandbox.commands.run( `cd /home/user/repo && amp --dangerously-allow-all -x "Add error handling to all API endpoints"`, { onStdout: (data) => process.stdout.write(data) } ) const diff = await sandbox.commands.run('cd /home/user/repo && git diff') console.log(diff.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("amp", envs={ "AMP_API_KEY": os.environ["AMP_API_KEY"], }, timeout=600) sandbox.git.clone("https://github.com/your-org/your-repo.git", path="/home/user/repo", username="x-access-token", password=os.environ["GITHUB_TOKEN"], depth=1, ) result = sandbox.commands.run( 'cd /home/user/repo && amp --dangerously-allow-all -x "Add error handling to all API endpoints"', on_stdout=lambda data: print(data, end=""), ) diff = sandbox.commands.run("cd /home/user/repo && git diff") print(diff.stdout) sandbox.kill() ``` ## Streaming JSON Use `--stream-json` to get a real-time JSONL event stream with rich metadata — including tool calls, token usage, thinking blocks, and permission decisions. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('amp', { envs: { AMP_API_KEY: process.env.AMP_API_KEY }, }) const result = await sandbox.commands.run( `cd /home/user/repo && amp --dangerously-allow-all --stream-json -x "Find and fix all TODO comments"`, { onStdout: (data) => { for (const line of data.split('\n').filter(Boolean)) { const event = JSON.parse(line) if (event.type === 'assistant') { console.log(`[assistant] tokens: ${event.message.usage?.output_tokens}`) } else if (event.type === 'result') { console.log(`[done] ${event.message.subtype} in ${event.message.duration_ms}ms`) } } }, } ) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("amp", envs={ "AMP_API_KEY": os.environ["AMP_API_KEY"], }) def handle_event(data): for line in data.strip().split("\n"): if line: event = json.loads(line) if event["type"] == "assistant": usage = event.get("message", {}).get("usage", {}) print(f"[assistant] tokens: {usage.get('output_tokens')}") elif event["type"] == "result": msg = event["message"] print(f"[done] {msg['subtype']} in {msg['duration_ms']}ms") result = sandbox.commands.run( 'cd /home/user/repo && amp --dangerously-allow-all --stream-json -x "Find and fix all TODO comments"', on_stdout=handle_event, ) sandbox.kill() ``` ## Thread management Amp persists conversations as threads that can be resumed or continued with follow-up tasks. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('amp', { envs: { AMP_API_KEY: process.env.AMP_API_KEY }, timeoutMs: 600_000, }) // Start a new thread const initial = await sandbox.commands.run( `cd /home/user/repo && amp --dangerously-allow-all -x "Analyze the codebase and create a refactoring plan"`, { onStdout: (data) => process.stdout.write(data) } ) // List threads and get the most recent thread ID const threads = await sandbox.commands.run('amp threads list --json') const threadId = JSON.parse(threads.stdout)[0].id // Continue the thread with a follow-up task const followUp = await sandbox.commands.run( `cd /home/user/repo && amp threads continue ${threadId} --dangerously-allow-all -x "Now implement step 1 of the plan"`, { onStdout: (data) => process.stdout.write(data) } ) const diff = await sandbox.commands.run('cd /home/user/repo && git diff') console.log(diff.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("amp", envs={ "AMP_API_KEY": os.environ["AMP_API_KEY"], }, timeout=600) # Start a new thread initial = sandbox.commands.run( 'cd /home/user/repo && amp --dangerously-allow-all -x "Analyze the codebase and create a refactoring plan"', on_stdout=lambda data: print(data, end=""), ) # List threads and get the most recent thread ID threads = sandbox.commands.run("amp threads list --json") thread_id = json.loads(threads.stdout)[0]["id"] # Continue the thread with a follow-up task follow_up = sandbox.commands.run( f'cd /home/user/repo && amp threads continue {thread_id} --dangerously-allow-all -x "Now implement step 1 of the plan"', on_stdout=lambda data: print(data, end=""), ) diff = sandbox.commands.run("cd /home/user/repo && git diff") print(diff.stdout) sandbox.kill() ``` ## Build a custom template If you need to customize the environment (e.g. pre-install dependencies, add config files), build your own template on top of the pre-built `amp` template. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b' export const template = Template() .fromTemplate('amp') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = ( Template() .from_template("amp") ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as ampTemplate } from './template' await Template.build(ampTemplate, 'my-amp', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from template import template as amp_template Template.build(amp_template, "my-amp", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` Run the build script to create the template. ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` ## Related guides Auto-pause, resume, and manage sandbox lifecycle Clone repos, manage branches, and push changes Connect to the sandbox via SSH for interactive sessions # Claude Code Source: https://e2b.mintlify.app/docs/agents/claude-code Run Claude Code in a secure E2B sandbox with full filesystem, terminal, and git access. [Claude Code](https://docs.anthropic.com/en/docs/claude-code) is Anthropic's agentic coding tool. E2B provides a pre-built `claude` template with Claude Code already installed. ## CLI Create a sandbox with the [E2B CLI](/docs/cli). ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sbx create claude ``` Once inside the sandbox, start Claude Code. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} claude ``` ## Run headless Use `-p` for non-interactive mode and `--dangerously-skip-permissions` to auto-approve all tool calls. The sandbox isolates the agent from your host machine, but sandboxes can reach the open internet by default — restrict outbound traffic with [network rules](/docs/network/internet-access) if the agent processes untrusted input or handles secrets. Auto-approving tool calls is contained by the sandbox: the agent cannot touch your host machine, local files, or credentials. It can still make outbound network requests — internet access is enabled by default. To limit where an auto-approved agent can connect, configure [outbound network rules](/docs/network/internet-access) (`allowInternetAccess`, plus allow/deny lists by CIDR or hostname). Hostname rules apply to HTTP(S) traffic only; use CIDR rules for other protocols. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('claude', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, }) const result = await sandbox.commands.run( `claude --dangerously-skip-permissions -p "Create a hello world HTTP server in Go"` ) console.log(result.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("claude", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }) result = sandbox.commands.run( 'claude --dangerously-skip-permissions -p "Create a hello world HTTP server in Go"', ) print(result.stdout) sandbox.kill() ``` ### Example: work on a cloned repository ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('claude', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, timeoutMs: 600_000, }) await sandbox.git.clone('https://github.com/your-org/your-repo.git', { path: '/home/user/repo', username: 'x-access-token', password: process.env.GITHUB_TOKEN, depth: 1, }) const result = await sandbox.commands.run( `cd /home/user/repo && claude --dangerously-skip-permissions -p "Add error handling to all API endpoints"`, { onStdout: (data) => process.stdout.write(data) } ) const diff = await sandbox.commands.run('cd /home/user/repo && git diff') console.log(diff.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("claude", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }, timeout=600) sandbox.git.clone("https://github.com/your-org/your-repo.git", path="/home/user/repo", username="x-access-token", password=os.environ["GITHUB_TOKEN"], depth=1, ) result = sandbox.commands.run( 'cd /home/user/repo && claude --dangerously-skip-permissions -p "Add error handling to all API endpoints"', on_stdout=lambda data: print(data, end=""), ) diff = sandbox.commands.run("cd /home/user/repo && git diff") print(diff.stdout) sandbox.kill() ``` ## Structured output Use `--output-format json` to get machine-readable responses — useful for building pipelines or extracting specific results. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('claude', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, }) const result = await sandbox.commands.run( `claude --dangerously-skip-permissions --output-format json -p "Review this codebase and list all security issues as JSON"` ) const response = JSON.parse(result.stdout) console.log(response) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("claude", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }) result = sandbox.commands.run( 'claude --dangerously-skip-permissions --output-format json -p "Review this codebase and list all security issues as JSON"', ) response = json.loads(result.stdout) print(response) sandbox.kill() ``` ## Streaming output Use `--output-format stream-json` to get a real-time JSONL event stream — including tool calls, token usage, and result metadata. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('claude', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, }) const result = await sandbox.commands.run( `cd /home/user/repo && claude --dangerously-skip-permissions --output-format stream-json -p "Find and fix all TODO comments"`, { onStdout: (data) => { for (const line of data.split('\n').filter(Boolean)) { const event = JSON.parse(line) if (event.type === 'assistant') { console.log(`[assistant] tokens: ${event.message.usage?.output_tokens}`) } else if (event.type === 'result') { console.log(`[done] ${event.subtype} in ${event.duration_ms}ms`) } } }, } ) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("claude", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }) def handle_event(data): for line in data.strip().split("\n"): if line: event = json.loads(line) if event["type"] == "assistant": usage = event.get("message", {}).get("usage", {}) print(f"[assistant] tokens: {usage.get('output_tokens')}") elif event["type"] == "result": print(f"[done] {event['subtype']} in {event['duration_ms']}ms") result = sandbox.commands.run( 'cd /home/user/repo && claude --dangerously-skip-permissions --output-format stream-json -p "Find and fix all TODO comments"', on_stdout=handle_event, ) sandbox.kill() ``` ## Resume a session Claude Code persists conversations that can be resumed with follow-up tasks using `--resume`. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('claude', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, timeoutMs: 600_000, }) // Start a new session const initial = await sandbox.commands.run( `cd /home/user/repo && claude --dangerously-skip-permissions --output-format json -p "Analyze the codebase and create a refactoring plan"` ) // Extract session ID from the JSON response const response = JSON.parse(initial.stdout) const sessionId = response.session_id // Continue with a follow-up task const followUp = await sandbox.commands.run( `cd /home/user/repo && claude --dangerously-skip-permissions --resume ${sessionId} -p "Now implement step 1 of the plan"`, { onStdout: (data) => process.stdout.write(data) } ) const diff = await sandbox.commands.run('cd /home/user/repo && git diff') console.log(diff.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("claude", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }, timeout=600) # Start a new session initial = sandbox.commands.run( 'cd /home/user/repo && claude --dangerously-skip-permissions --output-format json -p "Analyze the codebase and create a refactoring plan"', ) # Extract session ID from the JSON response response = json.loads(initial.stdout) session_id = response["session_id"] # Continue with a follow-up task follow_up = sandbox.commands.run( f'cd /home/user/repo && claude --dangerously-skip-permissions --resume {session_id} -p "Now implement step 1 of the plan"', on_stdout=lambda data: print(data, end=""), ) diff = sandbox.commands.run("cd /home/user/repo && git diff") print(diff.stdout) sandbox.kill() ``` ## Custom system prompt Write a `CLAUDE.md` file into the sandbox for project context or use `--system-prompt` to provide task-specific instructions. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('claude', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, }) // Write project context await sandbox.files.write('/home/user/repo/CLAUDE.md', ` You are working on a Go microservice. Always use structured logging with slog. Follow the project's error handling conventions in pkg/errors. `) const result = await sandbox.commands.run( `cd /home/user/repo && claude --dangerously-skip-permissions -p "Add a /healthz endpoint"` ) console.log(result.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("claude", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }) # Write project context sandbox.files.write("/home/user/repo/CLAUDE.md", """ You are working on a Go microservice. Always use structured logging with slog. Follow the project's error handling conventions in pkg/errors. """) result = sandbox.commands.run( 'cd /home/user/repo && claude --dangerously-skip-permissions -p "Add a /healthz endpoint"', ) print(result.stdout) sandbox.kill() ``` ## Connect MCP tools Claude Code has built-in support for [MCP](https://modelcontextprotocol.io/). E2B provides an [MCP gateway](/docs/mcp) that gives Claude access to 200+ tools from the [Docker MCP Catalog](https://hub.docker.com/mcp). ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('claude', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, mcp: { browserbase: { apiKey: process.env.BROWSERBASE_API_KEY, projectId: process.env.BROWSERBASE_PROJECT_ID, }, }, }) const mcpUrl = sandbox.getMcpUrl() const mcpToken = await sandbox.getMcpToken() await sandbox.commands.run( `claude mcp add --transport http e2b-mcp-gateway ${mcpUrl} --header "Authorization: Bearer ${mcpToken}"` ) const result = await sandbox.commands.run( `claude --dangerously-skip-permissions -p "Use browserbase to research E2B and summarize your findings"`, { onStdout: console.log } ) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("claude", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }, mcp={ "browserbase": { "apiKey": os.environ["BROWSERBASE_API_KEY"], "projectId": os.environ["BROWSERBASE_PROJECT_ID"], }, }) mcp_url = sandbox.get_mcp_url() mcp_token = sandbox.get_mcp_token() sandbox.commands.run( f'claude mcp add --transport http e2b-mcp-gateway {mcp_url} --header "Authorization: Bearer {mcp_token}"', ) result = sandbox.commands.run( 'claude --dangerously-skip-permissions -p "Use browserbase to research E2B and summarize your findings"', on_stdout=lambda data: print(data, end=""), ) sandbox.kill() ``` ## Build a custom template If you need to customize the environment (e.g. pre-install dependencies, add config files), build your own template on top of the pre-built `claude` template. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b' export const template = Template() .fromTemplate('claude') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = ( Template() .from_template("claude") ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as claudeCodeTemplate } from './template' await Template.build(claudeCodeTemplate, 'my-claude', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from template import template as claude_code_template Template.build(claude_code_template, "my-claude", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` Run the build script to create the template. ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` ## Related guides Connect Claude Code to 200+ MCP tools Auto-pause, resume, and manage sandbox lifecycle Clone repos, manage branches, and push changes # Claude Managed Agents Source: https://e2b.mintlify.app/docs/agents/claude-managed-agents Use E2B as the sandbox runtime for Claude Managed Agents self-hosted environments. [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) can use a self-hosted environment when you want tool calls to run in your own infrastructure. Claude runs the agentic loop and reasoning process; E2B provides isolated sandbox infrastructure for the environment where tool calls execute. The reusable `E2B/claude-managed-agents-webhooks` template receives webhooks and routes each Claude Managed Agents session to a persistent E2B worker sandbox. This separation is intentional for security: Claude decides what work to do, while the sandbox contains the execution environment, filesystem, tools, network access, and runtime logs. For the full source, local setup scripts, and app-owned routing examples, see the [Claude Managed Agents cookbook](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-managed-agents). ## Install dependencies ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npm install e2b @anthropic-ai/sdk ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b anthropic ``` Export the values you will pass to the webhook sandbox: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # E2B dashboard API keys: https://e2b.dev/dashboard?tab=keys export E2B_API_KEY="..." # Claude Console API keys: https://console.anthropic.com/settings/keys export ANTHROPIC_API_KEY="..." # Claude Console environments: https://platform.claude.com/workspaces/default/environments export ANTHROPIC_ENVIRONMENT_ID="..." # Open the self-hosted environment in the Claude Console and click Generate environment key. export ANTHROPIC_ENVIRONMENT_KEY="..." ``` ## Start the webhook sandbox Start the public template with auto-resume enabled. The template starts a webhook server on port `8000`. Because the signing key only appears after you register a webhook endpoint, write the router config now and add the signing key to the same sandbox after registration. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('E2B/claude-managed-agents-webhooks', { lifecycle: { onTimeout: 'pause', autoResume: true }, }) await sandbox.files.write([ { path: '/opt/anthropic-managed-agents-js/config/e2b-api-key', data: `${process.env.E2B_API_KEY}\n`, }, { path: '/opt/anthropic-managed-agents-js/config/anthropic-api-key', data: `${process.env.ANTHROPIC_API_KEY}\n`, }, { path: '/opt/anthropic-managed-agents-js/config/anthropic-environment-id', data: `${process.env.ANTHROPIC_ENVIRONMENT_ID}\n`, }, { path: '/opt/anthropic-managed-agents-js/config/anthropic-environment-key', data: `${process.env.ANTHROPIC_ENVIRONMENT_KEY}\n`, }, ]) console.log(`Webhook URL: https://${sandbox.getHost(8000)}/webhook`) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create( "E2B/claude-managed-agents-webhooks", lifecycle={"on_timeout": "pause", "auto_resume": True}, ) sandbox.files.write( "/opt/anthropic-managed-agents-js/config/e2b-api-key", f"{os.environ['E2B_API_KEY']}\n", ) sandbox.files.write( "/opt/anthropic-managed-agents-js/config/anthropic-api-key", f"{os.environ['ANTHROPIC_API_KEY']}\n", ) sandbox.files.write( "/opt/anthropic-managed-agents-js/config/anthropic-environment-id", f"{os.environ['ANTHROPIC_ENVIRONMENT_ID']}\n", ) sandbox.files.write( "/opt/anthropic-managed-agents-js/config/anthropic-environment-key", f"{os.environ['ANTHROPIC_ENVIRONMENT_KEY']}\n", ) print(f"Webhook URL: https://{sandbox.get_host(8000)}/webhook") ``` At this point `/health` returns `200`, but `/webhook` returns `503` until the signing key is configured. Keep this sandbox running or paused; registering a different sandbox URL means writing the signing key into that sandbox instead. ## Register the webhook In the [Claude Console webhooks settings](https://platform.claude.com/settings/workspaces/default/webhooks), create a webhook endpoint using the printed URL: ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} https:///webhook ``` Subscribe it to: ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} session.status_run_started ``` Save the generated signing key, export it locally, then write it into the same webhook sandbox. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} export ANTHROPIC_WEBHOOK_SIGNING_KEY="whsec_..." ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} await sandbox.files.write( '/opt/anthropic-managed-agents-js/config/anthropic-webhook-signing-key', `${process.env.ANTHROPIC_WEBHOOK_SIGNING_KEY}\n`, ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sandbox.files.write( "/opt/anthropic-managed-agents-js/config/anthropic-webhook-signing-key", f"{os.environ['ANTHROPIC_WEBHOOK_SIGNING_KEY']}\n", ) ``` ## Run an end-to-end smoke test Create or select a Claude Managed Agents agent in the [Claude Console](https://platform.claude.com/workspaces/default/agents), then create a session with that agent and the same `ANTHROPIC_ENVIRONMENT_ID` you wrote into the webhook sandbox. Creating the session does not start work; the `user.message` event does. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} export ANTHROPIC_AGENT_ID="agent_..." ``` Use a small shell task for the first smoke test: ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Use bash to echo webhook-smoke-ok. Answer only with that text. ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import Claude from '@anthropic-ai/sdk' const client = new Claude({ apiKey: process.env.ANTHROPIC_API_KEY, }) const session = await client.beta.sessions.create({ agent: process.env.ANTHROPIC_AGENT_ID!, environment_id: process.env.ANTHROPIC_ENVIRONMENT_ID!, title: 'E2B webhook smoke', }) await client.beta.sessions.events.send(session.id, { events: [ { type: 'user.message', content: [ { type: 'text', text: 'Use bash to echo webhook-smoke-ok. Answer only with that text.', }, ], }, ], }) console.log(`Session ID: ${session.id}`) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from anthropic import Anthropic as Claude client = Claude(api_key=os.environ["ANTHROPIC_API_KEY"]) session = client.beta.sessions.create( agent=os.environ["ANTHROPIC_AGENT_ID"], environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"], title="E2B webhook smoke", ) client.beta.sessions.events.send( session.id, events=[ { "type": "user.message", "content": [ { "type": "text", "text": "Use bash to echo webhook-smoke-ok. Answer only with that text.", }, ], }, ], ) print(f"Session ID: {session.id}") ``` Claude should answer: ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} webhook-smoke-ok ``` Then check the webhook sandbox: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const health = await fetch(`https://${sandbox.getHost(8000)}/health`) console.log(await health.json()) const logs = await sandbox.commands.run( 'tail -200 /opt/anthropic-managed-agents-js/webhook.log || true', ) console.log(logs.stdout) const assignments = await sandbox.commands.run( 'cat /opt/anthropic-managed-agents-js/.managed-agent-sandbox-store.json || true', ) console.log(assignments.stdout) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import json import urllib.request with urllib.request.urlopen(f"https://{sandbox.get_host(8000)}/health") as response: print(json.loads(response.read())) logs = sandbox.commands.run( "tail -200 /opt/anthropic-managed-agents-js/webhook.log || true", ) print(logs.stdout) assignments = sandbox.commands.run( "cat /opt/anthropic-managed-agents-js/.managed-agent-sandbox-store.json || true", ) print(assignments.stdout) ``` A healthy run has: * `/health` returning `ok: true`. * Router logs showing `routing work` and `assigned session`. * A session-to-sandbox assignment in the router's assignment store. * The assigned worker sandbox's `worker.log` showing the shell tool execution and successful results posted back to Claude. * The Claude Managed Agents session returning to `session.status_idle`. The assignment store records the worker sandbox ID for each routed session. Connect to that sandbox and check `/opt/anthropic-managed-agents-js/worker.log` when you need the tool execution logs. If the session stays at `requires_action`, check `/opt/anthropic-managed-agents-js/webhook.log` in the router sandbox first, then check `/opt/anthropic-managed-agents-js/worker.log` in the assigned worker sandbox. Stale environment keys, missing signing keys, archived sessions, and failed tool-result posts show up there. ## Runtime behavior The worker sandbox runs tool calls with `/mnt/session` as its workdir. File tools are constrained to that workdir, skills are downloaded under `/mnt/session/skills//`, and generated artifacts should be written under `/mnt/session/outputs`. The webhook sandbox is the router. It keeps a session-to-sandbox assignment store and starts worker sandboxes with E2B auto-resume and pause-on-timeout settings. ## Session-scoped sandboxes By default, the public webhook template gives each Claude Managed Agents session its own E2B worker sandbox. Follow-up turns for the same session reconnect to the same worker and reuse its `/mnt/session` filesystem. The sandbox is the isolated execution environment, not the place where Claude's reasoning loop runs. Use [cloud buckets](/docs/storage/cloud-buckets), [Archil](/docs/storage/archil), or [volumes](/docs/volumes) when files need to outlive a sandbox or be shared across many sandboxes. If you want to run that router in your own service instead of in E2B, use the cookbook's [`app-webhooks/` example](https://github.com/e2b-dev/e2b-cookbook/tree/main/examples/anthropic-managed-agents/javascript/app-webhooks). It receives webhooks in your app, claims work there, and routes each session to its own E2B sandbox by default. ## Clean up Remove the webhook endpoint in the Claude Console before deleting the router sandbox. Then kill the router sandbox and any assigned worker sandboxes you no longer need. ## Related guides Full JavaScript and Python examples for polling workers, sandbox-hosted webhooks, and app-owned routing. Pause and resume sandboxes to preserve filesystem state between runs. Store generated files outside the sandbox filesystem. # Codex Source: https://e2b.mintlify.app/docs/agents/codex Run OpenAI Codex in a secure E2B sandbox with full filesystem, terminal, and git access. [Codex](https://github.com/openai/codex) is OpenAI's open-source coding agent. E2B provides a pre-built `codex` template with Codex already installed. ## CLI Create a sandbox with the [E2B CLI](/docs/cli). ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sbx create codex ``` Once inside the sandbox, start Codex. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} codex ``` ## Run headless Use `codex exec` for non-interactive mode and `--full-auto` to auto-approve tool calls. The sandbox isolates the agent from your host machine, but sandboxes can reach the open internet by default — restrict outbound traffic with [network rules](/docs/network/internet-access). Pass `--skip-git-repo-check` to bypass git directory ownership checks inside the sandbox. Pass `CODEX_API_KEY` as an environment variable. Auto-approving tool calls is contained by the sandbox: the agent cannot touch your host machine, local files, or credentials. It can still make outbound network requests — internet access is enabled by default. To limit where an auto-approved agent can connect, configure [outbound network rules](/docs/network/internet-access) (`allowInternetAccess`, plus allow/deny lists by CIDR or hostname). Hostname rules apply to HTTP(S) traffic only; use CIDR rules for other protocols. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('codex', { envs: { CODEX_API_KEY: process.env.CODEX_API_KEY }, }) const result = await sandbox.commands.run( `codex exec --full-auto --skip-git-repo-check "Create a hello world HTTP server in Go"` ) console.log(result.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("codex", envs={ "CODEX_API_KEY": os.environ["CODEX_API_KEY"], }) result = sandbox.commands.run( 'codex exec --full-auto --skip-git-repo-check "Create a hello world HTTP server in Go"', ) print(result.stdout) sandbox.kill() ``` ### Example: work on a cloned repository Use `-C` to set Codex's working directory to a cloned repo. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('codex', { envs: { CODEX_API_KEY: process.env.CODEX_API_KEY }, timeoutMs: 600_000, }) await sandbox.git.clone('https://github.com/your-org/your-repo.git', { path: '/home/user/repo', username: 'x-access-token', password: process.env.GITHUB_TOKEN, depth: 1, }) const result = await sandbox.commands.run( `codex exec --full-auto --skip-git-repo-check -C /home/user/repo "Add error handling to all API endpoints"`, { onStdout: (data) => process.stdout.write(data) } ) const diff = await sandbox.commands.run('cd /home/user/repo && git diff') console.log(diff.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("codex", envs={ "CODEX_API_KEY": os.environ["CODEX_API_KEY"], }, timeout=600) sandbox.git.clone("https://github.com/your-org/your-repo.git", path="/home/user/repo", username="x-access-token", password=os.environ["GITHUB_TOKEN"], depth=1, ) result = sandbox.commands.run( 'codex exec --full-auto --skip-git-repo-check -C /home/user/repo "Add error handling to all API endpoints"', on_stdout=lambda data: print(data, end=""), ) diff = sandbox.commands.run("cd /home/user/repo && git diff") print(diff.stdout) sandbox.kill() ``` ## Schema-validated output Use `--output-schema` to constrain the agent's final response to a JSON Schema. This ensures the output conforms to a specific structure — useful for building reliable pipelines. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('codex', { envs: { CODEX_API_KEY: process.env.CODEX_API_KEY }, }) await sandbox.files.write('/home/user/schema.json', JSON.stringify({ type: 'object', properties: { issues: { type: 'array', items: { type: 'object', properties: { file: { type: 'string' }, line: { type: 'number' }, severity: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] }, description: { type: 'string' }, }, required: ['file', 'severity', 'description'], }, }, }, required: ['issues'], })) const result = await sandbox.commands.run( `codex exec --full-auto --skip-git-repo-check --output-schema /home/user/schema.json -C /home/user/repo "Review this codebase for security issues"` ) const response = JSON.parse(result.stdout) console.log(response.issues) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("codex", envs={ "CODEX_API_KEY": os.environ["CODEX_API_KEY"], }) sandbox.files.write("/home/user/schema.json", json.dumps({ "type": "object", "properties": { "issues": { "type": "array", "items": { "type": "object", "properties": { "file": {"type": "string"}, "line": {"type": "number"}, "severity": {"type": "string", "enum": ["low", "medium", "high", "critical"]}, "description": {"type": "string"}, }, "required": ["file", "severity", "description"], }, }, }, "required": ["issues"], })) result = sandbox.commands.run( 'codex exec --full-auto --skip-git-repo-check --output-schema /home/user/schema.json -C /home/user/repo "Review this codebase for security issues"', ) response = json.loads(result.stdout) print(response["issues"]) sandbox.kill() ``` ## Streaming events Use `--json` to get a JSONL event stream. Each line is a JSON object representing an agent event (tool calls, file changes, messages). Progress goes to stderr; events go to stdout. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('codex', { envs: { CODEX_API_KEY: process.env.CODEX_API_KEY }, }) const result = await sandbox.commands.run( `codex exec --full-auto --skip-git-repo-check --json -C /home/user/repo "Refactor the utils module into separate files"`, { onStdout: (data) => { for (const line of data.split('\n').filter(Boolean)) { const event = JSON.parse(line) console.log(`[${event.type}]`, event) } }, } ) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("codex", envs={ "CODEX_API_KEY": os.environ["CODEX_API_KEY"], }) def handle_event(data): for line in data.strip().split("\n"): if line: event = json.loads(line) print(f"[{event['type']}]", event) result = sandbox.commands.run( 'codex exec --full-auto --skip-git-repo-check --json -C /home/user/repo "Refactor the utils module into separate files"', on_stdout=handle_event, ) sandbox.kill() ``` ## Resume a session Codex persists sessions (rollout files under `~/.codex/sessions` in the sandbox) that can be resumed with follow-up tasks using `codex exec resume`. Run the first task with `--json` and capture the `thread_id` from the `thread.started` event. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('codex', { envs: { CODEX_API_KEY: process.env.CODEX_API_KEY }, timeoutMs: 600_000, }) // Start a new session const initial = await sandbox.commands.run( `codex exec --full-auto --skip-git-repo-check --json "Create plan.md with a 3-step plan for a TODO CLI app"` ) // The first --json event is thread.started const threadId = JSON.parse(initial.stdout.trim().split('\n')[0]).thread_id // Continue in the same session await sandbox.commands.run( `codex exec resume ${threadId} --full-auto --skip-git-repo-check "Now implement step 1 of the plan"`, { onStdout: (data) => process.stdout.write(data) } ) const plan = await sandbox.commands.run('cat /home/user/plan.md') console.log(plan.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import json from e2b import Sandbox sandbox = Sandbox.create("codex", envs={ "CODEX_API_KEY": os.environ["CODEX_API_KEY"], }, timeout=600) # Start a new session initial = sandbox.commands.run( 'codex exec --full-auto --skip-git-repo-check --json "Create plan.md with a 3-step plan for a TODO CLI app"', ) # The first --json event is thread.started thread_id = json.loads(initial.stdout.strip().splitlines()[0])["thread_id"] # Continue in the same session sandbox.commands.run( f'codex exec resume {thread_id} --full-auto --skip-git-repo-check "Now implement step 1 of the plan"', on_stdout=lambda data: print(data, end=""), ) plan = sandbox.commands.run("cat /home/user/plan.md") print(plan.stdout) sandbox.kill() ``` To simply continue the most recent session in the working directory, use `codex exec resume --last "follow-up task"` — no thread ID needed. When working in a cloned repo, pass `-C` before the `resume` subcommand (`codex exec -C /home/user/repo resume "..."`); it is not accepted after it. ## Image input Pass screenshots or design mockups with `--image` to give Codex visual context alongside the prompt. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import fs from 'fs' import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('codex', { envs: { CODEX_API_KEY: process.env.CODEX_API_KEY }, timeoutMs: 600_000, }) // Upload a design mockup to the sandbox await sandbox.files.write( '/home/user/mockup.png', fs.readFileSync('./mockup.png') ) const result = await sandbox.commands.run( `codex exec --full-auto --skip-git-repo-check --image /home/user/mockup.png -C /home/user/repo "Implement this UI design as a React component"`, { onStdout: (data) => process.stdout.write(data) } ) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("codex", envs={ "CODEX_API_KEY": os.environ["CODEX_API_KEY"], }, timeout=600) # Upload a design mockup to the sandbox with open("./mockup.png", "rb") as f: sandbox.files.write("/home/user/mockup.png", f) result = sandbox.commands.run( 'codex exec --full-auto --skip-git-repo-check --image /home/user/mockup.png -C /home/user/repo "Implement this UI design as a React component"', on_stdout=lambda data: print(data, end=""), ) sandbox.kill() ``` ## Build a custom template If you need to customize the environment (e.g. pre-install dependencies, add config files), build your own template on top of the pre-built `codex` template. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b' export const template = Template() .fromTemplate('codex') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = ( Template() .from_template("codex") ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as codexTemplate } from './template' await Template.build(codexTemplate, 'my-codex', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from template import template as codex_template Template.build(codex_template, "my-codex", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` Run the build script to create the template. ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` ## Related guides Auto-pause, resume, and manage sandbox lifecycle Clone repos, manage branches, and push changes Connect to the sandbox via SSH for interactive sessions # Crabbox with E2B Source: https://e2b.mintlify.app/docs/agents/crabbox Run Crabbox test and execution workflows on E2B sandboxes. [Crabbox](https://github.com/openclaw/crabbox) is a remote software testing and execution control plane for maintainers and AI agents. Use its E2B provider when you want Crabbox to create an E2B Linux sandbox, sync your working tree, run a command, stream output, and clean up from one CLI command. ## Install Install Crabbox locally. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} brew install openclaw/tap/crabbox crabbox --version ``` ## Configure E2B Export your E2B API key before running Crabbox. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} export E2B_API_KEY=e2b_... ``` You can select E2B per command with `--provider e2b`, or set it in a repo-level Crabbox config. ```yaml .crabbox.yaml theme={"theme":{"light":"github-light","dark":"github-dark-default"}} provider: e2b target: linux e2b: template: base workdir: crabbox ``` `template` is the E2B template to create. `workdir` is the directory where Crabbox syncs and runs your project inside the sandbox. ## Run tests Run a command in a fresh E2B sandbox. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} crabbox run --provider e2b -- echo crabbox-e2b-ok ``` Use an existing warm sandbox for repeated runs. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} crabbox warmup --provider e2b --e2b-template base lease="" crabbox run --provider e2b --id "$lease" -- echo crabbox-e2b-ok crabbox status --provider e2b --id "$lease" --wait crabbox stop --provider e2b "$lease" ``` Run in a custom E2B workdir. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} crabbox run --provider e2b --e2b-workdir repo -- echo crabbox-e2b-ok ``` ## How it works With `provider: e2b`, Crabbox uses E2B sandbox lifecycle, file upload, and process APIs instead of SSH. Crabbox owns local config, sync manifests, command orchestration, output streaming, and normalized `list` / `status` behavior. E2B owns the sandbox runtime and command transport. ## Limitations * E2B is a Linux delegated-run provider for Crabbox. * Crabbox SSH commands such as `ssh`, `vnc`, `code`, and SSH-based Actions hydration are not available with `provider: e2b`. * `--class` and `--type` are rejected because sandbox resources come from the selected E2B template. * `--e2b-template`, `--e2b-workdir`, `--e2b-user`, `--e2b-api-url`, and `--e2b-domain` override matching config values. For the full Crabbox command and provider reference, see the [Crabbox repository](https://github.com/openclaw/crabbox). # LangChain Deep Agents Source: https://e2b.mintlify.app/docs/agents/deep-agents Use E2B sandboxes as the execution backend for LangChain Deep Agents. [Deep Agents](https://docs.langchain.com/oss/python/deepagents/overview) is LangChain's agent harness for building LLM-powered agents, built on the [LangGraph](https://langchain-ai.github.io/langgraph/) runtime. In Deep Agents, a [sandbox backend](https://docs.langchain.com/oss/python/deepagents/sandboxes) defines the environment where the agent operates — E2B is a supported backend via the [`langchain-e2b`](https://pypi.org/project/langchain-e2b/) package. With the E2B backend, the agent's built-in tools all execute inside an isolated sandbox instead of your machine: * **Filesystem tools** — `ls`, `read_file`, `write_file`, `edit_file`, `glob`, and `grep` operate on the sandbox filesystem. * **Shell tool** — `execute` runs arbitrary shell commands in the sandbox. ## Install the dependencies Set API keys for E2B and your model provider: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} export E2B_API_KEY="e2b_***" # get yours at https://e2b.dev/dashboard export ANTHROPIC_API_KEY="sk-ant-***" ``` Then install the packages: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install deepagents langchain-e2b e2b ``` ## Basic example Create an E2B sandbox, wrap it in the `E2BSandbox` backend, and pass it to `create_deep_agent`. You manage the sandbox lifecycle yourself — create it before the agent runs and kill it when you're done. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from deepagents import create_deep_agent from e2b import Sandbox from langchain_e2b import E2BSandbox sandbox = Sandbox.create(timeout=600) agent = create_deep_agent( model="anthropic:claude-sonnet-5", system_prompt="You are a coding agent working in an isolated Linux sandbox.", backend=E2BSandbox(sandbox=sandbox), ) result = agent.invoke({ "messages": [{ "role": "user", "content": "Create hello.py that prints 'Hello from E2B', run it, and show the output.", }] }) print(result["messages"][-1].content) sandbox.kill() ``` The agent's file operations and shell commands all run inside the sandbox — nothing touches your machine. Because the backend wraps the native E2B SDK sandbox, you can configure everything the SDK supports: [custom templates](/docs/template/quickstart), timeouts, [persistence](/docs/sandbox/persistence), or reconnecting to an existing sandbox by ID. ## Deep Agents Code (dcode) [Deep Agents Code](https://docs.langchain.com/oss/python/deepagents/sandboxes#deep-agents-code) is LangChain's terminal coding agent built on Deep Agents. `langchain-e2b` ships a sandbox provider for it, so every `dcode` session can run in an E2B sandbox: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} dcode --install langchain-e2b --package export E2B_API_KEY="e2b_***" dcode --sandbox e2b ``` Requires `dcode` >= 0.1.19 and `langchain-e2b` >= 0.0.4. Unlike the library path, `dcode` manages the sandbox lifecycle for you — it creates the sandbox on start and deletes it on exit. Configure it with: * `E2B_TEMPLATE` — custom sandbox template to start from * `E2B_SANDBOX_TIMEOUT` — sandbox timeout in seconds ## Custom templates By default, sandboxes start from the E2B base template. To pre-install languages, frameworks, or tools your agent needs — and cut setup time per run — build a [custom template](/docs/template/quickstart) and pass it when creating the sandbox: ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sandbox = Sandbox.create(template="your-template-id-or-name", timeout=600) ``` ## Deep Agents vs. Open SWE [Open SWE](/docs/agents/open-swe) is LangChain's coding agent framework built on top of Deep Agents — both use the same `langchain-e2b` backend under the hood, but they sit at different layers: | | **Deep Agents** (this page) | **[LangChain Open SWE](/docs/agents/open-swe)** | | ----------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | | What it is | A Python library you embed in your own agent | A deployable internal coding agent you fork and run | | How you use E2B | Create the sandbox yourself and pass `E2BSandbox` to `create_deep_agent` | Set `SANDBOX_TYPE="e2b"` — sandboxes are created and managed for you | | Sandbox lifecycle | Yours to manage (`Sandbox.create()` / `sandbox.kill()`) | Per-thread persistent sandbox, reconnect by ID, auto-recreate | | Use it when | Building any custom agent that needs isolated code execution | You want a GitHub/Slack/Linear-driven coding agent for your org | Use this page's approach for custom agents; if you're deploying a coding agent for your team, start from [Open SWE](/docs/agents/open-swe) instead. ## Learn more * [Deep Agents sandboxes](https://docs.langchain.com/oss/python/deepagents/sandboxes) — sandbox backends overview * [E2B integration reference](https://docs.langchain.com/oss/python/integrations/sandboxes/e2b) — LangChain's E2B provider page * [`langchain-e2b` on PyPI](https://pypi.org/project/langchain-e2b/) ## Related guides Build custom sandbox templates with pre-installed dependencies Auto-pause, resume, and manage sandbox lifecycle LangChain's coding agent framework with E2B built in # Devin Source: https://e2b.mintlify.app/docs/agents/devin Run Devin for Terminal in a secure E2B sandbox. [Devin for Terminal](https://devin.ai/terminal) is Cognition's coding agent for working directly from a terminal. You can install it in an E2B sandbox and use the sandbox as an isolated workspace for agent tasks. ## Installation Use a PTY for install and login because Devin's setup is interactive. You can create a Devin account during sign up if you don't have one. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sandbox create base ### # Inside the sandbox ### curl -fsSL https://cli.devin.ai/install.sh | bash source /home/user/.bashrc ``` ## Usage After installation and login, use `devin -p` for non-interactive mode and `--permission-mode dangerous` to auto-approve tool calls. Auto-approving tool calls is contained by the sandbox: the agent cannot touch your host machine, local files, or credentials. It can still make outbound network requests — internet access is enabled by default. To limit where an auto-approved agent can connect, configure [outbound network rules](/docs/network/internet-access) (`allowInternetAccess`, plus allow/deny lists by CIDR or hostname). Hostname rules apply to HTTP(S) traffic only; use CIDR rules for other protocols. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} mkdir -p /home/user/project cd /home/user/project devin --permission-mode dangerous -p "Create a hello world HTTP server in Go" ``` ## Example: work on a cloned repository After installing Devin for Terminal and signing in, clone a repository and run Devin from the project directory. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} git clone https://github.com/your-org/your-repo.git /home/user/repo cd /home/user/repo devin --permission-mode dangerous -p "Add error handling to all API endpoints" ``` Devin can edit files, run commands, and work on code inside the sandbox without accessing your local machine. # Google ADK Source: https://e2b.mintlify.app/docs/agents/google-adk Give Google ADK agents a secure E2B sandbox to run code, commands, and files in. [Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) is Google's framework for building agents, with Gemini as the default model. The [E2B](https://e2b.dev/) integration gives ADK workloads an isolated remote workspace for running code and commands instead of executing them on your host. ## Choose an integration Use Google's built-in E2B environment for direct shell and file operations Give an ADK agent ready-made code, command, file, and background-process tools Choose the **native environment** when your application directly controls the sandbox. Choose the **advanced plugin** when the agent itself should decide when to run code, manage files, or start a service. ## Native: Google ADK environment Google ADK includes the experimental [`E2BEnvironment`](https://github.com/google/adk-python/tree/main/src/google/adk/integrations/e2b) for direct command execution and file access through ADK's environment API. No additional integration package is required. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install "google-adk[e2b]>=2.3.0" ``` Create and close the environment explicitly in your application: ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import asyncio from google.adk.integrations.e2b import E2BEnvironment async def main() -> None: env = E2BEnvironment() await env.initialize() try: await env.write_file("hello.py", 'print("Hello from E2B")\n') result = await env.execute("python hello.py") print(result.stdout) finally: await env.close() asyncio.run(main()) ``` This path exposes the sandbox as a low-level workspace: your application calls `execute`, `read_file`, and `write_file` itself. ## Advanced: E2B plugin The [`e2b-adk`](https://pypi.org/project/e2b-adk/) plugin exposes sandbox operations as tools the model can call. It adds stateful code execution, shell commands, file management, and background processes backed by the [E2B Code Interpreter](/docs). The plugin creates one sandbox lazily, shares it across tool calls, and closes it with the ADK runner. To use E2B with ADK: 1. Create an `E2BPlugin`. 2. Hand `plugin.get_tools()` to your `Agent` and register the plugin on the `App`. 3. Run the agent — every tool call executes inside the sandbox, and the plugin creates and tears the sandbox down for you. ### Install the dependencies Install the plugin. It pulls in Google ADK and the E2B Code Interpreter SDK. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b-adk ``` You need an E2B API key for the sandbox and a Gemini key for the model. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} export E2B_API_KEY="..." export GOOGLE_API_KEY="..." ``` ## Example: Data analysis agent A good fit for a sandbox is analysis the model shouldn't do in its head. Here the agent is given a raw dataset and a question. Rather than eyeballing the numbers, it writes the data to a file, runs real pandas against it, and reports figures it actually computed — the sandbox is a calculator it can't fool. ### Create the plugin and agent The plugin owns the sandbox. Pass its tools to the `Agent` and register it on the `App`; the instruction is what makes the agent *run* code rather than guess. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from google.adk.agents import Agent from google.adk.apps import App from e2b_adk import E2BPlugin INSTRUCTION = """You are a data analyst. You never guess numbers — you compute them. Save any data you are given to a file with write_file, analyse it by running real pandas with run_code, fix and re-run if the code errors, and report the figures you computed. Never report a number you have not verified by running code.""" plugin = E2BPlugin() agent = Agent( model="gemini-2.5-flash", name="data_analyst", instruction=INSTRUCTION, tools=plugin.get_tools(), ) app = App(name="data_analysis", root_agent=agent, plugins=[plugin]) ``` ### Run the analysis Run the agent inside an `InMemoryRunner`. The sandbox is created lazily on the first tool call, kept alive while the agent works, and killed when the `async with` block exits — you never manage it yourself. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from google.adk.runners import InMemoryRunner DATASET = """month,region,marketing_spend,revenue 2024-01,North,12000,48000 2024-02,North,15000,61000 2024-03,North,9000,37000 2024-04,North,18000,72000 2024-01,South,8000,26000 2024-02,South,11000,30000 2024-03,South,14000,33000 2024-04,South,17000,38000""" async with InMemoryRunner(app=app) as runner: # run_debug prints the conversation as it runs. await runner.run_debug( f"Here is marketing spend and revenue as CSV:\n\n{DATASET}\n\n" "Save it to sales.csv, then tell me which region converts spend into " "revenue more efficiently and which month performed best." ) ``` The agent writes `sales.csv` with `write_file`, then uses `run_code` to load it with pandas, compute revenue-per-dollar and per-month totals, and answer in prose — every number backed by an execution in the sandbox: ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} data_analyst > North converts marketing spend into revenue more efficiently — about $4.0 of revenue per $1 of spend versus roughly $2.5 for South. The best single month was 2024-04 in North: $72,000 revenue from $18,000 spend. ``` Pass `verbose=True` to `run_debug` to also print each tool call and its result as the agent works. ### Full example ```python expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import asyncio from google.adk.agents import Agent from google.adk.apps import App from google.adk.runners import InMemoryRunner from e2b_adk import E2BPlugin INSTRUCTION = """You are a data analyst. You never guess numbers — you compute them. Save any data you are given to a file with write_file, analyse it by running real pandas with run_code, fix and re-run if the code errors, and report the figures you computed. Never report a number you have not verified by running code.""" DATASET = """month,region,marketing_spend,revenue 2024-01,North,12000,48000 2024-02,North,15000,61000 2024-03,North,9000,37000 2024-04,North,18000,72000 2024-01,South,8000,26000 2024-02,South,11000,30000 2024-03,South,14000,33000 2024-04,South,17000,38000""" async def main() -> None: plugin = E2BPlugin(metadata={"example": "data-analysis"}) agent = Agent( model="gemini-2.5-flash", name="data_analyst", instruction=INSTRUCTION, tools=plugin.get_tools(), ) app = App(name="data_analysis", root_agent=agent, plugins=[plugin]) async with InMemoryRunner(app=app) as runner: # run_debug prints the conversation as it runs. await runner.run_debug( f"Here is marketing spend and revenue as CSV:\n\n{DATASET}\n\n" "Save it to sales.csv, then tell me which region converts spend " "into revenue more efficiently and which month performed best." ) asyncio.run(main()) ``` ## Example: Code generation agent The same tools support a code generator that verifies its own work. The instruction tells the agent to execute every snippet in the sandbox before returning it, so what you get back has already run and passed its tests — no untested code reaches the user. ```python expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import asyncio from google.adk.agents import Agent from google.adk.apps import App from google.adk.runners import InMemoryRunner from e2b_adk import E2BPlugin INSTRUCTION = """You are a code generator that returns verified, working code. For every request: (1) write the function, (2) write tests, (3) EXECUTE in the sandbox with run_code, (4) if it fails, fix and re-run until tests pass, (5) return ONLY the final function. Never return code you haven't executed.""" async def main() -> None: plugin = E2BPlugin(metadata={"example": "code-generator"}) agent = Agent( model="gemini-2.5-flash", name="codegen", instruction=INSTRUCTION, tools=plugin.get_tools(), ) app = App(name="codegen", root_agent=agent, plugins=[plugin]) async with InMemoryRunner(app=app) as runner: # run_debug prints the conversation as it runs. await runner.run_debug( "Write a Python function group_by(items, key) that groups a list " "by a key function." ) asyncio.run(main()) ``` ## Tools `plugin.get_tools()` returns six tools, all sharing the plugin's single sandbox so state persists across calls. Each returns a dict with a `success` flag and reports failures in the result rather than raising, so a bad call never aborts the agent run. `success` means the tool *ran*: code that raised or a command that exited non-zero still returns `success: True` with the failure captured in `error` / `exit_code` — only a call that could not run at all returns `success: False`. | Tool | Does | | -------------------------- | --------------------------------------------------------------------- | | `run_code` | Run code in a stateful kernel (variables persist across calls) | | `run_command` | Run a shell command | | `write_file` / `read_file` | Write and read files in the sandbox | | `list_files` | List a directory | | `start_background_command` | Start a long-running process, with an optional preview URL for a port | ## Configuration `E2BPlugin` accepts keyword-only options, all optional. Anything you don't set falls back to the E2B SDK's own default — the plugin overrides none of them. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} plugin = E2BPlugin( api_key=None, # defaults to the E2B_API_KEY env var template=None, # E2B sandbox template metadata=None, # dict[str, str] attached to the sandbox envs=None, # environment variables inside the sandbox timeout=None, # sandbox timeout in seconds (re-applied on every tool call) lifecycle=None, # what happens on timeout — see below # ...every other AsyncSandbox.create() option is forwarded verbatim ) ``` The plugin keeps the sandbox alive while the agent is working: every tool call pushes the expiry window forward by `timeout` (E2B's default is 300s). An idle gap longer than `timeout` still expires the sandbox under E2B's default lifecycle — pass `lifecycle={"on_timeout": {"action": "pause"}, "auto_resume": True}` to pause and auto-resume across idle gaps instead. See the [repository README](https://github.com/e2b-dev/e2b-adk-plugin#configuration) for the full option list. ## Reference examples Complete, runnable scripts live in the plugin repository. Compute real answers from a dataset with pandas in the sandbox Return only code that has been executed and tested # Grok Build Source: https://e2b.mintlify.app/docs/agents/grok Run Grok Build in a secure E2B sandbox with full filesystem, terminal, and git access. [Grok Build](https://x.ai/cli) is xAI's coding agent and CLI. E2B provides a pre-built `grok` template with Grok Build already installed. ## CLI Create a sandbox with the [E2B CLI](/docs/cli). ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sbx create grok ``` Once inside the sandbox, start Grok Build. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} grok ``` ## Run headless Use `-p` for non-interactive mode and `--always-approve` to auto-approve all tool calls. The sandbox isolates the agent from your host machine, but sandboxes can reach the open internet by default — restrict outbound traffic with [network rules](/docs/network/internet-access). Grok Build authenticates with an API key from the [xAI console](https://console.x.ai) via the `XAI_API_KEY` environment variable. Auto-approving tool calls is contained by the sandbox: the agent cannot touch your host machine, local files, or credentials. It can still make outbound network requests — internet access is enabled by default. To limit where an auto-approved agent can connect, configure [outbound network rules](/docs/network/internet-access) (`allowInternetAccess`, plus allow/deny lists by CIDR or hostname). Hostname rules apply to HTTP(S) traffic only; use CIDR rules for other protocols. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('grok', { envs: { XAI_API_KEY: process.env.XAI_API_KEY }, }) const result = await sandbox.commands.run( `grok --always-approve -p "Create a hello world HTTP server in Go"` ) console.log(result.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("grok", envs={ "XAI_API_KEY": os.environ["XAI_API_KEY"], }) result = sandbox.commands.run( 'grok --always-approve -p "Create a hello world HTTP server in Go"', ) print(result.stdout) sandbox.kill() ``` ### Example: work on a cloned repository ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('grok', { envs: { XAI_API_KEY: process.env.XAI_API_KEY }, timeoutMs: 600_000, }) await sandbox.git.clone('https://github.com/your-org/your-repo.git', { path: '/home/user/repo', username: 'x-access-token', password: process.env.GITHUB_TOKEN, depth: 1, }) const result = await sandbox.commands.run( `cd /home/user/repo && grok --always-approve -p "Add error handling to all API endpoints"`, { onStdout: (data) => process.stdout.write(data) } ) const diff = await sandbox.commands.run('cd /home/user/repo && git diff') console.log(diff.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("grok", envs={ "XAI_API_KEY": os.environ["XAI_API_KEY"], }, timeout=600) sandbox.git.clone("https://github.com/your-org/your-repo.git", path="/home/user/repo", username="x-access-token", password=os.environ["GITHUB_TOKEN"], depth=1, ) result = sandbox.commands.run( 'cd /home/user/repo && grok --always-approve -p "Add error handling to all API endpoints"', on_stdout=lambda data: print(data, end=""), ) diff = sandbox.commands.run("cd /home/user/repo && git diff") print(diff.stdout) sandbox.kill() ``` ## Build a custom template If you need to customize the environment (e.g. pre-install dependencies, add config files), build your own template on top of the pre-built `grok` template. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b' export const template = Template() .fromTemplate('grok') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = ( Template() .from_template("grok") ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as grokTemplate } from './template' await Template.build(grokTemplate, 'my-grok', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from template import template as grok_template Template.build(grok_template, "my-grok", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` Run the build script to create the template. ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` ## Related guides Auto-pause, resume, and manage sandbox lifecycle Clone repos, manage branches, and push changes Connect to the sandbox via SSH for interactive sessions # LangChain Open SWE Source: https://e2b.mintlify.app/docs/agents/open-swe Use E2B sandboxes as the execution environment for Open SWE, LangChain's open-source coding agent framework. [Open SWE](https://github.com/langchain-ai/open-swe) is LangChain's open-source framework for building your org's internal coding agent, built on [LangGraph](https://langchain-ai.github.io/langgraph/) and [Deep Agents](https://github.com/langchain-ai/deepagents). Every task runs in an isolated cloud sandbox where the agent gets full shell access — and E2B is a supported sandbox provider out of the box. ## Get started ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} git clone https://github.com/langchain-ai/open-swe.git cd open-swe uv venv source .venv/bin/activate uv sync --all-extras ``` Get your API key from the [E2B dashboard](https://e2b.dev/dashboard?tab=keys) and create a `.env` file in the repo root — `langgraph dev` loads it automatically: ```bash .env theme={"theme":{"light":"github-light","dark":"github-dark-default"}} SANDBOX_TYPE="e2b" E2B_API_KEY="e2b_***" # get yours at https://e2b.dev/dashboard E2B_TEMPLATE="my-template" # optional: custom sandbox template # === LLM === OPENAI_API_KEY="" # default model is openai:gpt-5.6-sol ANTHROPIC_API_KEY="" # when using anthropic: models ``` You need an API key for at least one LLM provider — the default model is `openai:gpt-5.6-sol`, and you can switch with `LLM_MODEL_ID` (e.g. `LLM_MODEL_ID="anthropic:claude-sonnet-5"`). Google and Fireworks are also supported — see the [full environment variable reference](https://github.com/langchain-ai/open-swe/blob/main/docs/INSTALLATION.md#6-environment-variables) for LangSmith tracing, GitHub App, and trigger configuration. No other sandbox changes are needed — Open SWE creates and manages the sandboxes for you. Open SWE's backend is a LangGraph app served together with its webhook API: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} langgraph dev ``` The full deployment (GitHub App, webhooks, dashboard) is covered in the [installation guide](https://github.com/langchain-ai/open-swe/blob/main/docs/INSTALLATION.md). As Open SWE picks up tasks, you can watch it create and reuse sandboxes with the [E2B CLI](/docs/cli): ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sandbox list --state running ``` ## Custom templates By default, sandboxes start from the E2B base template. To pre-install languages, frameworks, or internal tools your repositories depend on — and cut setup time per agent run — build a [custom template](/docs/template/quickstart) and point Open SWE at it: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} E2B_TEMPLATE="your-template-id-or-name" ``` ## Learn more * [Open SWE installation guide](https://github.com/langchain-ai/open-swe/blob/main/docs/INSTALLATION.md) * [Open SWE customization guide](https://github.com/langchain-ai/open-swe/blob/main/docs/CUSTOMIZATION.md#using-a-different-sandbox-provider) — switching sandbox providers * [Announcement blog post](https://blog.langchain.com/open-swe-an-open-source-framework-for-internal-coding-agents/) ## Related guides Build custom sandbox templates with pre-installed dependencies Auto-pause, resume, and manage sandbox lifecycle Clone repos, manage branches, and push changes # OpenAI Agents SDK Source: https://e2b.mintlify.app/docs/agents/openai-agents-sdk Use E2B sandboxes with the OpenAI Agents SDK. The [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) is a framework for building agentic workflows. E2B provides a native integration that lets you run `SandboxAgent` instances inside isolated E2B sandboxes — giving your agents full filesystem, terminal, and network access in a secure environment. To use E2B as the sandbox backend: 1. Create a sandbox session with `E2BSandboxClient`. 2. Build a `SandboxAgent` with your instructions and model. 3. Run the agent and pass the sandbox session through `RunConfig`. ## Install the dependencies Install the [OpenAI Agents SDK](https://pypi.org/project/openai-agents/) with the E2B extra to pull in the sandbox integration. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install openai-agents[e2b] ``` You will also need API keys for OpenAI and E2B. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} export OPENAI_API_KEY="..." export E2B_API_KEY="..." ``` ## Basic example Create an `E2BSandboxClient`, start a session, and run a `SandboxAgent` inside it. The agent gets full access to the sandbox environment — it can run commands, read and write files, and inspect the workspace. ### Create a session Initialize the `E2BSandboxClient` and create a sandbox session. The `pause_on_exit` option keeps the sandbox available after the script finishes so you can inspect its state. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents.extensions.sandbox import ( E2BSandboxClient, E2BSandboxClientOptions, E2BSandboxType, ) client = E2BSandboxClient() session = await client.create( options=E2BSandboxClientOptions( sandbox_type=E2BSandboxType.E2B, timeout=900, pause_on_exit=True, ) ) ``` ### Build and run the agent Define a `SandboxAgent` with a name, model, and instructions, then run it against the sandbox session using `Runner.run`. The result contains the agent's final output. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import ModelSettings, Runner from agents.run import RunConfig from agents.sandbox import SandboxAgent, SandboxRunConfig agent = SandboxAgent( name="Workspace Inspector", model="gpt-5.4", instructions=( "Inspect the workspace, explain what files exist, and summarize the project." ), model_settings=ModelSettings(tool_choice="required"), ) result = await Runner.run( agent, "Look around the workspace and summarize what you find.", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)), ) print(result.final_output) ``` ### Shut down Always shut down the session when you're done to release sandbox resources. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} await session.shutdown() ``` ### Full example The complete script that ties the steps above together. ```python expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import asyncio from agents import ModelSettings, Runner from agents.run import RunConfig from agents.sandbox import SandboxAgent, SandboxRunConfig from agents.extensions.sandbox import ( E2BSandboxClient, E2BSandboxClientOptions, E2BSandboxType, ) async def main() -> None: client = E2BSandboxClient() session = await client.create( options=E2BSandboxClientOptions( sandbox_type=E2BSandboxType.E2B, timeout=900, pause_on_exit=True, ) ) try: agent = SandboxAgent( name="Workspace Inspector", model="gpt-5.4", instructions=( "Inspect the workspace, explain what files exist, and summarize the project." ), model_settings=ModelSettings(tool_choice="required"), ) result = await Runner.run( agent, "Look around the workspace and summarize what you find.", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)), ) print(result.final_output) finally: await session.shutdown() asyncio.run(main()) ``` *** ## Build an app with multiple versions A common pattern is to start from the same starter app and create multiple versions in separate sandboxes — useful when comparing a first pass with a polished revision, or generating live preview URLs for each version. Based on the [`homepage_vite_basic_updated.ipynb`](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/e2b_demos/homepage_vite_basic_updated.ipynb) notebook from the Agents SDK repo. ### Define a manifest A `Manifest` describes the starter files your agent will work with. Each entry is a `File` with its content encoded as bytes. This lets you seed multiple sandboxes from the same baseline — useful when comparing different versions of an app. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents.sandbox import Manifest from agents.sandbox.entries import File def build_manifest() -> Manifest: return Manifest( entries={ "package.json": File(content=PACKAGE_JSON.encode()), "index.html": File(content=INDEX_HTML.encode()), "vite.config.js": File(content=VITE_CONFIG_JS.encode()), "src/main.tsx": File(content=MAIN_TSX.encode()), "src/App.tsx": File(content=APP_TSX.encode()), } ) ``` ### Create a sandbox session Create a sandbox session with the manifest, exposed ports for live previews, and internet access so the agent can install npm packages. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents.extensions.sandbox import ( E2BSandboxClient, E2BSandboxClientOptions, E2BSandboxType, ) session = await E2BSandboxClient().create( manifest=build_manifest(), options=E2BSandboxClientOptions( sandbox_type=E2BSandboxType.E2B, timeout=1800, exposed_ports=(4173,), allow_internet_access=True, pause_on_exit=True, ), ) await session.start() ``` ### Run the agent Build a `SandboxAgent` with capabilities like `ApplyPatch` and `Shell`, then run it against the sandbox session. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import ModelSettings, Runner from agents.run import RunConfig from agents.sandbox import SandboxAgent, SandboxRunConfig from agents.sandbox.capabilities import ApplyPatch, Shell agent = SandboxAgent( name="E2B Vite Builder", model="gpt-5.4-mini", instructions="Update the Vite app in the sandbox workspace and return a concise summary.", default_manifest=build_manifest(), capabilities=[ApplyPatch(), Shell()], model_settings=ModelSettings(tool_choice="required"), ) result = await Runner.run( agent, "Make the basic version now.", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)), ) ``` ### Start a preview server After the agent finishes, install dependencies, start the Vite dev server, and resolve the exposed port to get a live preview URL. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} await session.exec( "sh", "-lc", ( "npm install >/tmp/e2b-demo-npm-install.log 2>&1 && " "nohup npm run dev -- --host 0.0.0.0 --port 4173 " ">/tmp/e2b-demo-vite.log 2>&1 &" ), shell=False, timeout=120, ) preview_url = (await session.resolve_exposed_port(4173)).url_for("http") ``` ### Full example The complete `run_version()` helper ties all the steps above together. Call it once per version to get isolated sandboxes with their own preview URLs. Based on the [`homepage_vite_basic_updated.ipynb`](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/e2b_demos/homepage_vite_basic_updated.ipynb) notebook from the Agents SDK repo. ```python expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import ModelSettings, Runner from agents.run import RunConfig from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig from agents.sandbox.capabilities import ApplyPatch, Shell from agents.sandbox.entries import File from agents.extensions.sandbox import ( E2BSandboxClient, E2BSandboxClientOptions, E2BSandboxType, ) def build_manifest() -> Manifest: return Manifest( entries={ "package.json": File(content=PACKAGE_JSON.encode()), "index.html": File(content=INDEX_HTML.encode()), "vite.config.js": File(content=VITE_CONFIG_JS.encode()), "src/main.tsx": File(content=MAIN_TSX.encode()), "src/App.tsx": File(content=APP_TSX.encode()), } ) async def run_version(version_name: str, version_prompt: str) -> dict[str, str]: session = await E2BSandboxClient().create( manifest=build_manifest(), options=E2BSandboxClientOptions( sandbox_type=E2BSandboxType.E2B, timeout=1800, exposed_ports=(4173,), allow_internet_access=True, pause_on_exit=True, ), ) await session.start() agent = SandboxAgent( name=f"E2B Vite {version_name.title()} Builder", model="gpt-5.4-mini", instructions=( "Update the Vite app in the sandbox workspace and return a concise summary." ), developer_instructions=( f"Version goal: {version_prompt}\n" "Start from the tiny Vite starter. You may create src/styles.css if you want." ), default_manifest=build_manifest(), capabilities=[ApplyPatch(), Shell()], model_settings=ModelSettings(tool_choice="required"), ) result = await Runner.run( agent, f"Make the {version_name} version now.", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)), ) await session.exec( "sh", "-lc", ( "npm install >/tmp/e2b-demo-npm-install.log 2>&1 && " "nohup npm run dev -- --host 0.0.0.0 --port 4173 " ">/tmp/e2b-demo-vite.log 2>&1 &" ), shell=False, timeout=120, ) preview_url = (await session.resolve_exposed_port(4173)).url_for("http") return { "summary": str(result.final_output), "preview_url": preview_url, } ``` *** ## MCP-powered research agents You can create sandboxes with [MCP](https://modelcontextprotocol.io/) servers enabled, then connect the Agents SDK to the sandbox's MCP gateway. This gives you an agent that can discover sources with search-oriented MCP servers, verify pages in a browser, and keep all of that execution inside the sandbox. The Agents SDK repo includes a concrete example in [`deep_research_mcp.py`](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/e2b_demos/deep_research_mcp.py). ### Configure MCP servers Pass MCP server configurations when creating the sandbox session. This example enables [Browserbase](https://www.browserbase.com/) for browser automation and [Exa](https://exa.ai/) for search. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} session = await client.create( options=E2BSandboxClientOptions( sandbox_type=E2BSandboxType.E2B, timeout=900, pause_on_exit=True, mcp={ "browserbase": { "apiKey": os.environ["BROWSERBASE_API_KEY"], "geminiApiKey": os.environ["GEMINI_API_KEY"], "projectId": os.environ["BROWSERBASE_PROJECT_ID"], }, "exa": { "apiKey": os.environ["EXA_API_KEY"], }, }, ), ) ``` ### Connect to the MCP gateway Use `MCPServerStreamableHttp` to connect to the sandbox's MCP gateway. This gives the agent access to all the MCP servers you configured above. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents.mcp import MCPServerStreamableHttp async with MCPServerStreamableHttp( name="E2B MCP Gateway", params={ "url": mcp_url, "headers": {"Authorization": f"Bearer {mcp_token}"}, "timeout": 30, "sse_read_timeout": 300, }, ) as server: ... ``` ### Run the agent Pass the MCP server to the `SandboxAgent` via `mcp_servers`. The agent can now discover and call tools from all configured MCP servers. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import ModelSettings, Runner from agents.run import RunConfig from agents.sandbox import SandboxAgent, SandboxRunConfig agent = SandboxAgent( name="Deep Research Assistant", model="gpt-5.4", instructions=( "Use Exa to discover strong sources and Browserbase to verify important claims." ), mcp_servers=[server], model_settings=ModelSettings(tool_choice="required"), ) result = await Runner.run( agent, "Research browser automation infrastructure for AI agents.", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)), ) ``` ### Full example The complete script combining session creation, MCP gateway connection, and agent execution. ```python expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import ModelSettings, Runner from agents.mcp import MCPServerStreamableHttp from agents.run import RunConfig from agents.sandbox import SandboxAgent, SandboxRunConfig session = await client.create( options=E2BSandboxClientOptions( sandbox_type=E2BSandboxType.E2B, timeout=900, pause_on_exit=True, mcp={ "browserbase": { "apiKey": os.environ["BROWSERBASE_API_KEY"], "geminiApiKey": os.environ["GEMINI_API_KEY"], "projectId": os.environ["BROWSERBASE_PROJECT_ID"], }, "exa": { "apiKey": os.environ["EXA_API_KEY"], }, }, ), ) async with MCPServerStreamableHttp( name="E2B MCP Gateway", params={ "url": mcp_url, "headers": {"Authorization": f"Bearer {mcp_token}"}, "timeout": 30, "sse_read_timeout": 300, }, ) as server: agent = SandboxAgent( name="Deep Research Assistant", model="gpt-5.4", instructions=( "Use Exa to discover strong sources and Browserbase to verify important claims." ), mcp_servers=[server], model_settings=ModelSettings(tool_choice="required"), ) result = await Runner.run( agent, "Research browser automation infrastructure for AI agents.", run_config=RunConfig(sandbox=SandboxRunConfig(session=session)), ) ``` *** ## Parallel sandbox workers Use one coordinator agent to launch multiple specialized review or analysis lanes across separate sandboxes. Each lane runs in its own isolated environment, and the coordinator synthesizes the results into a single summary. The Agents SDK repo includes a concrete example in [`fullstack_code_review_parallel.py`](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/e2b_demos/fullstack_code_review_parallel.py). That example uses separate E2B-backed lanes for frontend review, backend review, and git tree review. ### Define the agents Create specialized `SandboxAgent` instances for each review lane, plus a regular `Agent` as the coordinator that will synthesize the results. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import Agent from agents.sandbox import SandboxAgent frontend_agent = SandboxAgent( name="Frontend Reviewer", model="gpt-5.4", instructions="Review the rendered frontend and identify UX and code risks.", ) backend_agent = SandboxAgent( name="Backend Reviewer", model="gpt-5.4", instructions="Review the API implementation and identify validation and auth risks.", ) coordinator = Agent( name="Review Coordinator", model="gpt-5.4", instructions=( "Run the available review lanes, compare their evidence, and produce a single summary." ), ) ``` ### Run the lanes Run each review agent against its own sandbox session. The lanes are independent and can run concurrently. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import Runner from agents.run import RunConfig from agents.sandbox import SandboxRunConfig frontend_result = await Runner.run( frontend_agent, "Review the frontend workspace.", run_config=RunConfig(sandbox=SandboxRunConfig(session=frontend_session)), ) backend_result = await Runner.run( backend_agent, "Review the backend workspace.", run_config=RunConfig(sandbox=SandboxRunConfig(session=backend_session)), ) ``` ### Synthesize the results Feed the findings from each lane into the coordinator agent to produce a single summary. ```python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} final_result = await Runner.run( coordinator, f"Frontend findings: {frontend_result.final_output}\n\n" f"Backend findings: {backend_result.final_output}", ) ``` ### Full example The complete script defining agents, running parallel review lanes, and synthesizing results. ```python expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from agents import Agent, Runner from agents.run import RunConfig from agents.sandbox import SandboxAgent, SandboxRunConfig frontend_agent = SandboxAgent( name="Frontend Reviewer", model="gpt-5.4", instructions="Review the rendered frontend and identify UX and code risks.", ) backend_agent = SandboxAgent( name="Backend Reviewer", model="gpt-5.4", instructions="Review the API implementation and identify validation and auth risks.", ) coordinator = Agent( name="Review Coordinator", model="gpt-5.4", instructions=( "Run the available review lanes, compare their evidence, and produce a single summary." ), ) frontend_result = await Runner.run( frontend_agent, "Review the frontend workspace.", run_config=RunConfig(sandbox=SandboxRunConfig(session=frontend_session)), ) backend_result = await Runner.run( backend_agent, "Review the backend workspace.", run_config=RunConfig(sandbox=SandboxRunConfig(session=backend_session)), ) final_result = await Runner.run( coordinator, f"Frontend findings: {frontend_result.final_output}\n\n" f"Backend findings: {backend_result.final_output}", ) ``` ## Reference examples The OpenAI Agents SDK repository includes several complete examples demonstrating E2B sandbox integration. Build and iterate on a Vite app across isolated sandboxes Research agent with Browserbase and Exa via MCP Multi-lane code review with a coordinator agent Getting started notebook for E2B sandbox integration # Deploy OpenClaw Source: https://e2b.mintlify.app/docs/agents/openclaw/openclaw-gateway Start the OpenClaw gateway in an E2B sandbox and connect your browser. ## Quick start This launches your OpenClaw [gateway](https://docs.openclaw.ai/gateway) site (web UI for chatting with agents). ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-gateway-token' const PORT = 18789 // 1. Create sandbox const sandbox = await Sandbox.create('openclaw', { envs: { OPENAI_API_KEY: process.env.OPENAI_API_KEY }, timeoutMs: 3600_000, }) // 2. Set the default model await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2') // 3. Set insecure control UI flags and start the gateway with token auth await sandbox.commands.run( `bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth true && ` + `openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true && ` + `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${TOKEN} --port ${PORT}'`, { background: true } ) // 4. Wait for the gateway to start listening for (let i = 0; i < 45; i++) { const probe = await sandbox.commands.run( `bash -lc 'ss -ltn | grep -q ":${PORT} " && echo ready || echo waiting'` ) if (probe.stdout.trim() === 'ready') break await new Promise((r) => setTimeout(r, 1000)) } const url = `https://${sandbox.getHost(PORT)}/?token=${TOKEN}` console.log(`Gateway: ${url}`) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os, time from e2b import Sandbox TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-gateway-token") PORT = 18789 # 1. Create sandbox sandbox = Sandbox.create("openclaw", envs={ "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"], }, timeout=3600) # 2. Set the default model sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2") # 3. Set insecure control UI flags and start the gateway with token auth sandbox.commands.run( f"bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth true && " f"openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true && " f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {TOKEN} --port {PORT}'", background=True, ) # 4. Wait for the gateway to start listening for _ in range(45): probe = sandbox.commands.run( f'bash -lc \'ss -ltn | grep -q ":{PORT} " && echo ready || echo waiting\'' ) if probe.stdout.strip() == "ready": break time.sleep(1) url = f"https://{sandbox.get_host(PORT)}/?token={TOKEN}" print(f"Gateway: {url}") ``` Visit the printed `Gateway` URL in your browser. If you run in secure mode (set `gateway.controlUi.dangerouslyDisableDeviceAuth false`), run this after opening the URL to poll pending pairing requests and approve the first one. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // 5. Poll for the browser's pending device request and approve it for (let i = 0; i < 30; i++) { try { const res = await sandbox.commands.run( `openclaw devices list --json --url ws://127.0.0.1:${PORT} --token ${TOKEN}` ) const data = JSON.parse(res.stdout) if (data.pending?.length) { const rid = data.pending[0].requestId await sandbox.commands.run( `openclaw devices approve ${rid} --token ${TOKEN} --url ws://127.0.0.1:${PORT}` ) console.log(`Device approved: ${rid}`) break } } catch {} await new Promise((r) => setTimeout(r, 2000)) } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import json # 5. Poll for the browser's pending device request and approve it for _ in range(30): try: res = sandbox.commands.run( f"openclaw devices list --json --url ws://127.0.0.1:{PORT} --token {TOKEN}" ) data = json.loads(res.stdout) if data.get("pending"): rid = data["pending"][0]["requestId"] sandbox.commands.run( f"openclaw devices approve {rid} --token {TOKEN} --url ws://127.0.0.1:{PORT}" ) print(f"Device approved: {rid}") break except Exception: pass time.sleep(2) ``` Once approved, the browser connects and the gateway UI loads. ## How it works | Step | What happens | | ---------------------------- | ---------------------------------------------------------------------------- | | `--bind lan` | Gateway listens on `0.0.0.0` so E2B can proxy it | | `--auth token` | Requires `?token=` on the URL for HTTP and WebSocket auth | | Browser opens URL | Gateway serves the UI, browser opens a WebSocket | | `code=1008 pairing required` | Gateway closes the WebSocket until the device is approved (secure mode only) | | `devices approve` | Approves the browser's device fingerprint (secure mode only) | | Browser reconnects | WebSocket connects successfully, UI is live | ## Gateway flags reference | Flag | Purpose | | ---------------------- | -------------------------------------------------- | | `--allow-unconfigured` | Start without a full config file | | `--bind lan` | Bind to `0.0.0.0` (required for E2B port proxying) | | `--auth token` | Enable token-based authentication | | `--token ` | The auth token (passed as `?token=` in the URL) | | `--port ` | Gateway listen port (default: `18789`) | ## How to restart the gateway Use this when the gateway is already running and you want a clean restart (for example, after changing model or env settings). We can't use the `openclaw gateway restart` command here. Some SDK environments cannot target a specific Unix user in `commands.run`. The commands below use the default command user context. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-gateway-token' const PORT = 18789 // 1) Kill existing gateway processes if present await sandbox.commands.run( `bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do for pid in $(pgrep -f "$p" || true); do kill "$pid" >/dev/null 2>&1 || true; done; done'` ) await new Promise((r) => setTimeout(r, 1000)) // 2) Start gateway again await sandbox.commands.run( `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${TOKEN} --port ${PORT}`, { background: true } ) // 3) Wait for listening socket for (let i = 0; i < 45; i++) { const probe = await sandbox.commands.run( `bash -lc 'ss -ltn | grep -q ":${PORT} " && echo ready || echo waiting'` ) if (probe.stdout.trim() === 'ready') break await new Promise((r) => setTimeout(r, 1000)) } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os, time TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-gateway-token") PORT = 18789 # 1) Kill existing gateway processes if present sandbox.commands.run( """bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do for pid in $(pgrep -f "$p" || true); do kill "$pid" >/dev/null 2>&1 || true done done'""" ) time.sleep(1) # 2) Start gateway again sandbox.commands.run( f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {TOKEN} --port {PORT}", background=True, ) # 3) Wait for listening socket for _ in range(45): probe = sandbox.commands.run( f'bash -lc \'ss -ltn | grep -q ":{PORT} " && echo ready || echo waiting\'' ) if probe.stdout.strip() == "ready": break time.sleep(1) ``` ## Turn insecure flags off (recommended after testing) Use this to restore secure device authentication after initial testing. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-gateway-token' const PORT = 18789 await sandbox.commands.run( `bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth false && ` + `openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth false'` ) await sandbox.commands.run( `bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do for pid in $(pgrep -f "$p" || true); do kill "$pid" >/dev/null 2>&1 || true; done; done'` ) await sandbox.commands.run( `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${TOKEN} --port ${PORT}`, { background: true } ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-gateway-token") PORT = 18789 sandbox.commands.run( "bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth false && " "openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth false'" ) sandbox.commands.run( """bash -lc 'for p in "[o]penclaw gateway" "[o]penclaw-gateway"; do for pid in $(pgrep -f "$p" || true); do kill "$pid" >/dev/null 2>&1 || true done done'""" ) sandbox.commands.run( f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {TOKEN} --port {PORT}", background=True, ) ``` ## Related Connect OpenClaw to Telegram and approve pairing # OpenClaw Telegram Source: https://e2b.mintlify.app/docs/agents/openclaw/openclaw-telegram Connect OpenClaw to Telegram in an E2B sandbox, approve pairing, and chat through your bot. OpenClaw supports Telegram as a chat channel. In E2B you can run OpenClaw in a sandbox, attach your bot token, and approve user pairing from the terminal. This guide covers the working flow we used: 1. Start OpenClaw in a sandbox. 2. Enable the Telegram plugin. 3. Add Telegram channel credentials. 4. Start the channel runtime in background. 5. Approve Telegram pairing. ## Prerequisites * A Telegram bot token from [@BotFather](https://t.me/BotFather). There's instructions to follow there, it runs /newbot for you and walks you through naming and creating your bot. * An OpenAI API key for the OpenClaw model. * E2B API key configured locally. ## Quick start ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const GATEWAY_PORT = 18789 const GATEWAY_TOKEN = process.env.OPENCLAW_APP_TOKEN || 'my-openclaw-token' const sandbox = await Sandbox.create('openclaw', { envs: { OPENAI_API_KEY: process.env.OPENAI_API_KEY, TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN, }, timeoutMs: 3600_000, }) await sandbox.commands.run('openclaw config set agents.defaults.model.primary openai/gpt-5.2') // Enable the Telegram plugin (required before adding the channel) await sandbox.commands.run('openclaw config set plugins.entries.telegram.enabled true') await sandbox.commands.run('openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"') await sandbox.commands.run( `bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth true && ` + `openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true && ` + `openclaw gateway --allow-unconfigured --bind lan --auth token --token ${GATEWAY_TOKEN} --port ${GATEWAY_PORT}'`, { background: true } ) for (let i = 0; i < 45; i++) { const probe = await sandbox.commands.run( `bash -lc 'ss -ltn | grep -q ":${GATEWAY_PORT} " && echo ready || echo waiting'` ) if (probe.stdout.trim() === 'ready') break await new Promise((r) => setTimeout(r, 1000)) } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import time from e2b import Sandbox GATEWAY_PORT = 18789 GATEWAY_TOKEN = os.environ.get("OPENCLAW_APP_TOKEN", "my-openclaw-token") sandbox = Sandbox.create("openclaw", envs={ "OPENAI_API_KEY": os.environ["OPENAI_API_KEY"], "TELEGRAM_BOT_TOKEN": os.environ["TELEGRAM_BOT_TOKEN"], }, timeout=3600) sandbox.commands.run("openclaw config set agents.defaults.model.primary openai/gpt-5.2") # Enable the Telegram plugin (required before adding the channel) sandbox.commands.run("openclaw config set plugins.entries.telegram.enabled true") sandbox.commands.run('openclaw channels add --channel telegram --token "$TELEGRAM_BOT_TOKEN"') sandbox.commands.run( f"bash -lc 'openclaw config set gateway.controlUi.allowInsecureAuth true && " f"openclaw config set gateway.controlUi.dangerouslyDisableDeviceAuth true && " f"openclaw gateway --allow-unconfigured --bind lan --auth token --token {GATEWAY_TOKEN} --port {GATEWAY_PORT}'", background=True, ) for _ in range(45): probe = sandbox.commands.run( f"bash -lc 'ss -ltn | grep -q \":{GATEWAY_PORT} \" && echo ready || echo waiting'" ) if probe.stdout.strip() == "ready": break time.sleep(1) ``` For Telegram setup, you do **not** need to open the gateway URL in a browser. The gateway process is used here as a long-running channel runtime. ## Pair your Telegram user 1. Open your bot in Telegram and send a message (for example: `hi`). 2. Telegram will return a pairing prompt similar to: ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} OpenClaw: access not configured. Your Telegram user id: ... Pairing code: XXXXXXXX Ask the bot owner to approve with: openclaw pairing approve telegram XXXXXXXX ``` 3. Approve that pairing code via `sandbox.commands.run(...)`: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const PAIRING_CODE = 'XXXXXXXX' // from Telegram await sandbox.commands.run( `openclaw pairing approve --channel telegram ${PAIRING_CODE}` ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} PAIRING_CODE = "XXXXXXXX" # from Telegram sandbox.commands.run( f"openclaw pairing approve --channel telegram {PAIRING_CODE}" ) ``` `openclaw pairing approve telegram ` also works if you prefer that form. ## Verify channel status ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const channels = await sandbox.commands.run('openclaw channels list --json') const status = await sandbox.commands.run('openclaw channels status --json --probe') const pairing = await sandbox.commands.run('openclaw pairing list --json --channel telegram') console.log(JSON.parse(channels.stdout)) console.log(JSON.parse(status.stdout)) console.log(JSON.parse(pairing.stdout)) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import json channels = sandbox.commands.run("openclaw channels list --json") status = sandbox.commands.run("openclaw channels status --json --probe") pairing = sandbox.commands.run("openclaw pairing list --json --channel telegram") print(json.loads(channels.stdout)) print(json.loads(status.stdout)) print(json.loads(pairing.stdout)) ``` If you need logs from channel handlers: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const logs = await sandbox.commands.run( 'openclaw channels logs --channel telegram --lines 200' ) console.log(logs.stdout) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} logs = sandbox.commands.run( "openclaw channels logs --channel telegram --lines 200" ) print(logs.stdout) ``` ## Troubleshooting * `Unknown channel: telegram` * The Telegram plugin is not enabled. Run `openclaw config set plugins.entries.telegram.enabled true` before adding the channel. * `OpenClaw: access not configured` * Pairing has not been approved yet. Run `openclaw pairing approve ...`. * `No API key found for provider ...` * This guide uses `openai/gpt-5.2`. Set `OPENAI_API_KEY` in sandbox envs. * No pending pairing requests from `pairing list` * Send a fresh message to the bot first, then retry `pairing list --channel telegram`. ## Related Run OpenClaw's web gateway with token auth # OpenCode Source: https://e2b.mintlify.app/docs/agents/opencode Run OpenCode in a secure E2B sandbox with full filesystem, terminal, and git access. [OpenCode](https://opencode.ai) is an open-source coding agent that supports multiple LLM providers. E2B provides a pre-built `opencode` template with OpenCode already installed. ## CLI Create a sandbox with the [E2B CLI](/docs/cli). ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sbx create opencode ``` Once inside the sandbox, start OpenCode. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} opencode ``` ## Run headless Use `opencode run` for non-interactive mode. Pass your LLM provider's API key as an environment variable — OpenCode supports `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, and [others](https://opencode.ai/docs/config/). ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('opencode', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, }) const result = await sandbox.commands.run( `opencode run "Create a hello world HTTP server in Go"` ) console.log(result.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("opencode", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }) result = sandbox.commands.run( 'opencode run "Create a hello world HTTP server in Go"', ) print(result.stdout) sandbox.kill() ``` ### Example: work on a cloned repository ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('opencode', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, timeoutMs: 600_000, }) await sandbox.git.clone('https://github.com/your-org/your-repo.git', { path: '/home/user/repo', username: 'x-access-token', password: process.env.GITHUB_TOKEN, depth: 1, }) const result = await sandbox.commands.run( `cd /home/user/repo && opencode run "Add error handling to all API endpoints"`, { onStdout: (data) => process.stdout.write(data) } ) const diff = await sandbox.commands.run('cd /home/user/repo && git diff') console.log(diff.stdout) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create("opencode", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }, timeout=600) sandbox.git.clone("https://github.com/your-org/your-repo.git", path="/home/user/repo", username="x-access-token", password=os.environ["GITHUB_TOKEN"], depth=1, ) result = sandbox.commands.run( 'cd /home/user/repo && opencode run "Add error handling to all API endpoints"', on_stdout=lambda data: print(data, end=""), ) diff = sandbox.commands.run("cd /home/user/repo && git diff") print(diff.stdout) sandbox.kill() ``` ## Connect with the OpenCode SDK OpenCode includes a [headless HTTP server](https://opencode.ai/docs/server/) that you can control programmatically using the [`@opencode-ai/sdk`](https://opencode.ai/docs/sdk/) client. Start the server inside a sandbox, get the public URL with `sandbox.getHost()`, and connect from your application. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' import { createOpencodeClient } from '@opencode-ai/sdk' const sandbox = await Sandbox.create('opencode', { envs: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }, lifecycle: { onTimeout: 'pause', // "pause" | "kill" }, timeoutMs: 10 * 60 * 1000, }) // Start the OpenCode server sandbox.commands.run('opencode serve --hostname 0.0.0.0 --port 4096', { background: true, }) // Wait for the server to be ready const host = sandbox.getHost(4096) const baseUrl = `https://${host}` while (true) { try { await fetch(`${baseUrl}/global/health`) break } catch { await new Promise((r) => setTimeout(r, 500)) } } // Connect to the server const client = createOpencodeClient({ baseUrl, }) // Create a session and send a prompt const { data: session } = await client.session.create({ body: { title: 'E2B Session' }, }) const { data: result } = await client.session.prompt({ path: { id: session.id }, body: { parts: [{ type: 'text', text: 'Create a hello world HTTP server in Go' }], }, }) console.log(result) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os import time import requests from e2b import Sandbox sandbox = Sandbox.beta_create("opencode", envs={ "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"], }, auto_pause=True, timeout=10 * 60) # Start the OpenCode server sandbox.commands.run( "opencode serve --hostname 0.0.0.0 --port 4096", background=True, ) # Wait for the server to be ready host = sandbox.get_host(4096) base_url = f"https://{host}" while True: try: requests.get(f"{base_url}/global/health") break except requests.ConnectionError: time.sleep(0.5) # Create a session and send a prompt via the HTTP API session = requests.post(f"{base_url}/session").json() result = requests.post( f"{base_url}/session/{session['id']}/message", json={ "parts": [{"type": "text", "text": "Create a hello world HTTP server in Go"}], }, ).json() print(result) ``` ## Build a custom template If you need to customize the environment (e.g. pre-install dependencies, add config files), build your own template on top of the pre-built `opencode` template. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template, waitForPort } from 'e2b' export const template = Template() .fromTemplate('opencode') .setEnvs({ OPENCODE_SERVER_PASSWORD: 'your-password', }) // Optional - start the OpenCode server on sandbox start .setStartCmd( 'opencode serve --hostname 0.0.0.0 --port 4096', waitForPort(4096) ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template, wait_for_port template = ( Template() .from_template("opencode") .set_envs({ "OPENCODE_SERVER_PASSWORD": "your-password", }) # Optional - start the OpenCode server on sandbox start .set_start_cmd( "opencode serve --hostname 0.0.0.0 --port 4096", wait_for_port(4096) ) ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as openCodeTemplate } from './template' await Template.build(openCodeTemplate, 'my-opencode', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from template import template as opencode_template Template.build(opencode_template, "my-opencode", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` Run the build script to create the template. ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` ## Related guides Auto-pause, resume, and manage sandbox lifecycle Clone repos, manage branches, and push changes Connect to the sandbox via SSH for interactive sessions # API key Source: https://e2b.mintlify.app/docs/api-key To use the API key, you can either: * **Set the API key as the `E2B_API_KEY` environment variable** to avoid passing it each time you create a sandbox. * Or pass it directly to the `Sandbox` constructor as shown below: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ apiKey: 'YOUR_API_KEY' }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create(api_key="YOUR_API_KEY") ``` ## Where to find API key You can get your API key at [dashboard](https://e2b.dev/dashboard?tab=keys).
# Access token `E2B_ACCESS_TOKEN` is deprecated. New access tokens can no longer be generated after **July 1, 2026**, and all access tokens stop working on **August 1, 2026**. Authenticate with `E2B_API_KEY` instead. See the [access token deprecation](/docs/migration/access-token-deprecation). The access token is used only by the interactive CLI login commands `e2b auth login` and `e2b auth configure`, which set up your local workstation. It is **not needed in the SDK**, and you do not need to set it as an environment variable to use the CLI. ## Where to find access token You can get your **Access token key** at the [dashboard](https://e2b.dev/dashboard/account). After July 1, 2026, new access tokens can no longer be generated. # Billing & limits Source: https://e2b.mintlify.app/docs/billing E2B uses [usage-based pricing](#usage-based-pricing) - you pay only for what you use. New users receive \$100 in free credits to get started. [Manage billing in dashboard](https://e2b.dev/dashboard?tab=billing) ## Plans | Feature | Hobby | Pro | Enterprise | | :-------------------------------------------- | ---------------- | ------------------------------ | ---------- | | **Base price** | \$0/month | \$150/month | Custom | | **Free credits** | \$100 (one-time) | No additional credits | Custom | | **Max vCPUs** | 8 | 8+ | Custom | | **Max memory** | 8 GB | 8+ GB | Custom | | **Disk size** | 10 GB | 20+ GB | Custom | | **Max continuous runtime** | 1 hour | 24 hours | Custom | | **Concurrent sandboxes** | 20 | 100 - 1,100 | 1,100+ | | **Concurrent builds** | 20 | 20 | Custom | | **Sandbox creation rate** | 1 / sec | 5 / sec | Custom | To upgrade your plan or purchase add-ons, visit the [dashboard billing tab](https://e2b.dev/dashboard?tab=billing). For Enterprise plans, [contact sales](mailto:enterprise@e2b.dev). *** ## Usage-based pricing You pay per second for compute resources while your sandbox is running. Upgrading to the Pro tier does not grant additional credits. It only increases the limits available to your team (such as concurrent sandboxes, continuous runtime, and resource ceilings). ### Compute costs Use the [usage cost calculator](https://e2b.dev/pricing#:~:text=Usage%20Cost%20Calculator) on our pricing page to estimate costs for your specific configuration. ### Customizing compute resources You can customize allocated CPU and RAM when building custom templates by specifying `cpuCount` and `memoryMB` in the build configuration. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, defaultBuildLogger } from 'e2b' await Template.build(template, 'my-template', { cpuCount: 8, memoryMB: 4096, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, default_build_logger Template.build( template, 'my-template', cpu_count=8, memory_mb=4096, on_build_logs=default_build_logger(), ) ``` See [template quickstart](/docs/template/quickstart) for more details on building custom templates. Need higher CPU or RAM limits? Contact [support@e2b.dev](mailto:support@e2b.dev) for more information. *** ## Monitoring usage Check your usage and costs in the [dashboard usage tab](https://e2b.dev/dashboard?tab=usage). *** ## FAQ Automatically at the start of the month for the previous month's usage. Your account will be blocked. Add a payment method to continue using E2B. Yes, you can set spending limits on the [budget page](https://e2b.dev/dashboard?tab=budget) in your dashboard. * **Enable auto-pause** - Automatically pause sandboxes after a period of inactivity to stop billing while preserving state * **Pause sandboxes when idle** - Use `sbx.pause()` to stop billing while keeping state available for later * **Kill sandboxes you no longer need** - Use `sbx.kill()` to stop billing and release resources permanently * **Allocate only what you need** - Start with default resources (2 vCPU, 512 MiB RAM) and increase only if necessary * **Monitor actively running sandboxes** - Use the [CLI](/docs/cli/list-sandboxes) or [dashboard](https://e2b.dev/dashboard?tab=usage) to track active sandboxes * **Use lifecycle events** - Set up [webhooks](/docs/sandbox/lifecycle-events-webhooks) to get notified when sandboxes are created No. You only pay while a sandbox is actively running. Once a sandbox is paused, killed or times out, billing stops immediately. # Analyze data with AI Source: https://e2b.mintlify.app/docs/code-interpreting/analyze-data-with-ai You can use E2B Sandbox to run AI-generated code to analyze data. Here's how the AI data analysis workflow usually looks like: 1. Your user has a dataset in CSV format or other formats. 2. You prompt the LLM to generate code (usually Python) based on the user's data. 3. The sandbox runs the AI-generated code and returns the results. 4. You display the results to the user. *** ## Example: Analyze CSV file with E2B and Claude 3.5 Sonnet This short example will show you how to use E2B Sandbox to run AI-generated code to analyze CSV data. ### Table of Contents 1. [Install dependencies](#1-install-dependencies) 2. [Set your API keys](#2-set-your-api-keys) 3. [Download example CSV file](#3-download-example-csv-file) 4. [Initialize the sandbox and upload the dataset to the sandbox](#4-initialize-the-sandbox-and-upload-the-dataset-to-the-sandbox) 5. [Prepare the method for running AI-generated code](#5-prepare-the-method-for-running-ai-generated-code) 6. [Prepare the prompt and initialize Anthropic client](#6-prepare-the-prompt-and-initialize-anthropic-client) 7. [Connect the sandbox to the LLM with tool calling](#7-connect-the-sandbox-to-the-llm-with-tool-calling) 8. [Parse the LLM response and run the AI-generated code in the sandbox](#8-parse-the-llm-response-and-run-the-ai-generated-code-in-the-sandbox) 9. [Save the generated chart](#9-save-the-generated-chart) 10. [Run the code](#10-run-the-code) 11. [Full final code](#full-final-code) ### 1. Install dependencies Install the E2B SDK and Claude SDK to your project by running the following command in your terminal. ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npm i @e2b/code-interpreter @anthropic-ai/sdk dotenv ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b-code-interpreter anthropic python-dotenv ``` ### 2. Set your API keys 1. Get your E2B API key from [E2B Dashboard](https://e2b.dev/dashboard?tab=keys). 2. Get your Claude API key from [Claude API Dashboard](https://console.anthropic.com/settings/keys). 3. Paste the keys into your `.env` file. ```bash .env theme={"theme":{"light":"github-light","dark":"github-dark-default"}} E2B_API_KEY=e2b_*** ANTHROPIC_API_KEY=sk-ant-*** ``` ### 3. Download example CSV file We'll be using the publicly available [dataset of about 10,000 movies](https://www.kaggle.com/datasets/muqarrishzaib/tmdb-10000-movies-dataset). 1. Click the "Download" button at the top of the page. 2. Select "Download as zip (2 MB)". 3. Unzip the file and you should see `dataset.csv` file. Move it to the root of your project. ### 4. Initialize the sandbox and upload the dataset to the sandbox We'll upload the dataset from the third step to the sandbox and save it as `dataset.csv` file. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import 'dotenv/config' import fs from 'fs' import { Sandbox } from '@e2b/code-interpreter' // Create sandbox const sbx = await Sandbox.create() // Upload the dataset to the sandbox const content = fs.readFileSync('dataset.csv') const datasetPathInSandbox = await sbx.files.write('dataset.csv', content) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from dotenv import load_dotenv load_dotenv() from e2b_code_interpreter import Sandbox # Create sandbox sbx = Sandbox.create() # Upload the dataset to the sandbox dataset_path_in_sandbox = "" with open("dataset.csv", "rb") as f: dataset_path_in_sandbox = sbx.files.write("dataset.csv", f) ``` ### 5. Prepare the method for running AI-generated code Add the following code to the file. Here we're adding the method for code execution. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // ... code from the previous step async function runAIGeneratedCode(aiGeneratedCode: string) { console.log('Running the code in the sandbox....') const execution = await sbx.runCode(aiGeneratedCode) console.log('Code execution finished!') console.log(execution) } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # ... code from the previous step def run_ai_generated_code(ai_generated_code: str): print('Running the code in the sandbox....') execution = sbx.run_code(ai_generated_code) print('Code execution finished!') print(execution) ``` ### 6. Prepare the prompt and initialize Anthropic client The prompt we'll be using describes the dataset and the analysis we want to perform like this: 1. Describe the columns in the CSV dataset. 2. Ask the LLM what we want to analyze - here we want to analyze the vote average over time. We're asking for a chart as the output. 3. Instruct the LLM to generate Python code for the data analysis. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import Anthropic from '@anthropic-ai/sdk' const prompt = ` I have a CSV file about movies. It has about 10k rows. It's saved in the sandbox at ${dataset_path_in_sandbox.path}. These are the columns: - 'id': number, id of the movie - 'original_language': string like "eng", "es", "ko", etc - 'original_title': string that's name of the movie in the original language - 'overview': string about the movie - 'popularity': float, from 0 to 9137.939. It's not normalized at all and there are outliers - 'release_date': date in the format yyyy-mm-dd - 'title': string that's the name of the movie in english - 'vote_average': float number between 0 and 10 that's representing viewers voting average - 'vote_count': int for how many viewers voted Write Python code that creates a line chart showing how vote_average changed over the years. Do NOT print or explore the data. Just create the visualization directly. CRITICAL: Your code MUST end with this exact line to display the plot: display(plt.gcf())` const anthropic = new Anthropic() console.log('Waiting for the model response...') const msg = await anthropic.messages.create({ model: 'claude-haiku-4-5-20251001', max_tokens: 1024, messages: [{ role: 'user', content: prompt }], }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from anthropic import Anthropic prompt = f''' I have a CSV file about movies. It has about 10k rows. It's saved in the sandbox at {dataset_path_in_sandbox.path}. These are the columns: - 'id': number, id of the movie - 'original_language': string like "eng", "es", "ko", etc - 'original_title': string that's name of the movie in the original language - 'overview': string about the movie - 'popularity': float, from 0 to 9137.939. It's not normalized at all and there are outliers - 'release_date': date in the format yyyy-mm-dd - 'title': string that's the name of the movie in english - 'vote_average': float number between 0 and 10 that's representing viewers voting average - 'vote_count': int for how many viewers voted Write Python code that creates a line chart showing how vote_average changed over the years. Do NOT print or explore the data. Just create the visualization directly. CRITICAL: Your code MUST end with this exact line to display the plot: display(plt.gcf())''' anthropic = Anthropic() msg = anthropic.messages.create( model='claude-haiku-4-5-20251001', max_tokens=1024, messages=[ {"role": "user", "content": prompt} ] ) ``` ### 7. Connect the sandbox to the LLM with tool calling We'll use Claude's ability to [use tools (function calling)](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) to run the code in the sandbox. The way we'll do it is by connecting the method for running AI-generated code we created in the previous step to the Claude model. Update the initialization of the Anthropic client to include the tool use like this: ```js JavaScript & TypeScript highlight={5-20} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const msg = await anthropic.messages.create({ model: 'claude-haiku-4-5-20251001', max_tokens: 1024, messages: [{ role: 'user', content: prompt }], tools: [ { name: 'run_python_code', description: 'Run Python code', input_schema: { type: 'object', properties: { code: { type: 'string', description: 'The Python code to run', }, }, required: ['code'], }, }, ], }) ``` ```python Python highlight={7-19} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} msg = anthropic.messages.create( model='claude-haiku-4-5-20251001', max_tokens=1024, messages=[ {"role": "user", "content": prompt} ], tools=[ { "name": "run_python_code", "description": "Run Python code", "input_schema": { "type": "object", "properties": { "code": { "type": "string", "description": "The Python code to run" }, }, "required": ["code"], }, }, ], ) ``` ### 8. Parse the LLM response and run the AI-generated code in the sandbox Now we'll parse the `msg` object to get the code from the LLM's response based on the tool we created in the previous step. Once we have the code, we'll pass it to the `runAIGeneratedCode` method in JavaScript or `run_ai_generated_code` method in Python we created in the previous step to run the code in the sandbox. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // ... code from the previous steps interface CodeRunToolInput { code: string } for (const contentBlock of msg.content) { if (contentBlock.type === 'tool_use') { if (contentBlock.name === 'run_python_code') { const code = (contentBlock.input as CodeRunToolInput).code console.log('Will run following code in the sandbox', code) // Execute the code in the sandbox await runAIGeneratedCode(code) } } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} for content_block in msg.content: if content_block.type == 'tool_use': if content_block.name == 'run_python_code': code = content_block.input['code'] print('Will run following code in the sandbox', code) # Execute the code in the sandbox run_ai_generated_code(code) ``` ### 9. Save the generated chart When running code in the sandbox for data analysis, you can get different types of results. Including stdout, stderr, charts, tables, text, runtime errors, and more. In this example we're specifically asking for a chart so we'll be looking for the chart in the results. Let's update the `runAIGeneratedCode` method in JavaScript and `run_ai_generated_code` method in Python to check for the chart in the results and save it to the file. ```js JavaScript & TypeScript highlight={7-13,16-18,21-25} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} async function runAIGeneratedCode(aiGeneratedCode: string) { console.log('Running the code in the sandbox....') const execution = await sbx.runCode(aiGeneratedCode) console.log('Code execution finished!') // First let's check if the code ran successfully. if (execution.error) { console.error('AI-generated code had an error.') console.log(execution.error.name) console.log(execution.error.value) console.log(execution.error.traceback) process.exit(1) } // Iterate over all the results and specifically check for png files that will represent the chart. let resultIdx = 0 for (const result of execution.results) { if (result.png) { // Save the png to a file // The png is in base64 format. fs.writeFileSync(`chart-${resultIdx}.png`, result.png, { encoding: 'base64' }) console.log(`Chart saved to chart-${resultIdx}.png`) resultIdx++ } } } ``` ```python Python highlight={1-2,7-12,15-17,20-23} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import sys import base64 def run_ai_generated_code(ai_generated_code: str): print('Running the code in the sandbox....') execution = sbx.run_code(ai_generated_code) print('Code execution finished!') # First let's check if the code ran successfully. if execution.error: print('AI-generated code had an error.') print(execution.error.name) print(execution.error.value) print(execution.error.traceback) sys.exit(1) # Iterate over all the results and specifically check for png files that will represent the chart. result_idx = 0 for result in execution.results: if result.png: # Save the png to a file # The png is in base64 format. with open(f'chart-{result_idx}.png', 'wb') as f: f.write(base64.b64decode(result.png)) print(f'Chart saved to chart-{result_idx}.png') result_idx += 1 ``` ### 10. Run the code Now you can run the whole code to see the results. ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx index.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python main.py ``` You should see the chart in the root of your project that will look similar to this: ### Full final code Check the full code in JavaScript and Python below: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import 'dotenv/config' import fs from 'fs' import Anthropic from '@anthropic-ai/sdk' import { Sandbox } from '@e2b/code-interpreter' // Create sandbox const sbx = await Sandbox.create() // Upload the dataset to the sandbox const content = fs.readFileSync('dataset.csv') const datasetPathInSandbox = await sbx.files.write('/home/user/dataset.csv', content) async function runAIGeneratedCode(aiGeneratedCode: string) { const execution = await sbx.runCode(aiGeneratedCode) if (execution.error) { console.error('AI-generated code had an error.') console.log(execution.error.name) console.log(execution.error.value) console.log(execution.error.traceback) process.exit(1) } // Iterate over all the results and specifically check for png files that will represent the chart. let resultIdx = 0 for (const result of execution.results) { if (result.png) { // Save the png to a file // The png is in base64 format. fs.writeFileSync(`chart-${resultIdx}.png`, result.png, { encoding: 'base64' }) console.log(`Chart saved to chart-${resultIdx}.png`) resultIdx++ } } } const prompt = ` I have a CSV file about movies. It has about 10k rows. It's saved in the sandbox at ${dataset_path_in_sandbox.path}. These are the columns: - 'id': number, id of the movie - 'original_language': string like "eng", "es", "ko", etc - 'original_title': string that's name of the movie in the original language - 'overview': string about the movie - 'popularity': float, from 0 to 9137.939. It's not normalized at all and there are outliers - 'release_date': date in the format yyyy-mm-dd - 'title': string that's the name of the movie in english - 'vote_average': float number between 0 and 10 that's representing viewers voting average - 'vote_count': int for how many viewers voted Write Python code that creates a line chart showing how vote_average changed over the years. Do NOT print or explore the data. Just create the visualization directly. CRITICAL: Your code MUST end with this exact line to display the plot: display(plt.gcf())` const anthropic = new Anthropic() console.log('Waiting for the model response...') const msg = await anthropic.messages.create({ model: 'claude-haiku-4-5-20251001', max_tokens: 1024, messages: [{ role: 'user', content: prompt }], tools: [ { name: 'run_python_code', description: 'Run Python code', input_schema: { type: 'object', properties: { code: { type: 'string', description: 'The Python code to run', }, }, required: ['code'], }, }, ], }) interface CodeRunToolInput { code: string } for (const contentBlock of msg.content) { if (contentBlock.type === 'tool_use') { if (contentBlock.name === 'run_python_code') { const code = (contentBlock.input as CodeRunToolInput).code console.log('Will run following code in the sandbox', code) // Execute the code in the sandbox await runAIGeneratedCode(code) } } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import sys import base64 from dotenv import load_dotenv load_dotenv() from e2b_code_interpreter import Sandbox from anthropic import Anthropic # Create sandbox sbx = Sandbox.create() # Upload the dataset to the sandbox with open("../dataset.csv", "rb") as f: dataset_path_in_sandbox = sbx.files.write("dataset.csv", f) def run_ai_generated_code(ai_generated_code: str): print('Running the code in the sandbox....') execution = sbx.run_code(ai_generated_code) print('Code execution finished!') # First let's check if the code ran successfully. if execution.error: print('AI-generated code had an error.') print(execution.error.name) print(execution.error.value) print(execution.error.traceback) sys.exit(1) # Iterate over all the results and specifically check for png files that will represent the chart. result_idx = 0 for result in execution.results: if result.png: # Save the png to a file # The png is in base64 format. with open(f'chart-{result_idx}.png', 'wb') as f: f.write(base64.b64decode(result.png)) print(f'Chart saved to chart-{result_idx}.png') result_idx += 1 prompt = f""" I have a CSV file about movies. It has about 10k rows. It's saved in the sandbox at {dataset_path_in_sandbox.path}. These are the columns: - 'id': number, id of the movie - 'original_language': string like "eng", "es", "ko", etc - 'original_title': string that's name of the movie in the original language - 'overview': string about the movie - 'popularity': float, from 0 to 9137.939. It's not normalized at all and there are outliers - 'release_date': date in the format yyyy-mm-dd - 'title': string that's the name of the movie in english - 'vote_average': float number between 0 and 10 that's representing viewers voting average - 'vote_count': int for how many viewers voted Write Python code that creates a line chart showing how vote_average changed over the years. Do NOT print or explore the data. Just create the visualization directly. CRITICAL: Your code MUST end with this exact line to display the plot: display(plt.gcf())""" anthropic = Anthropic() print("Waiting for model response...") msg = anthropic.messages.create( model='claude-haiku-4-5-20251001', max_tokens=1024, messages=[ {"role": "user", "content": prompt} ], tools=[ { "name": "run_python_code", "description": "Run Python code", "input_schema": { "type": "object", "properties": { "code": { "type": "string", "description": "The Python code to run" }, }, "required": ["code"] } } ] ) for content_block in msg.content: if content_block.type == "tool_use": if content_block.name == "run_python_code": code = content_block.input["code"] print("Will run following code in the sandbox", code) # Execute the code in the sandbox run_ai_generated_code(code) ``` # Pre-installed libraries Source: https://e2b.mintlify.app/docs/code-interpreting/analyze-data-with-ai/pre-installed-libraries The sandbox comes with a set of pre-installed Python libraries for data analysis but you can install additional packages # Code contexts Source: https://e2b.mintlify.app/docs/code-interpreting/contexts Run code in different code execution contexts with E2B Code Interpreter SDK. This allows you to parallelize code execution by running code in different contexts at the same time. By default the code is run in the Sandbox's default code execution context. You can change it by passing the `context` parameter to the `runCode()` method in JavaScript or `run_code()` in Python. ## Create a new Code Context You can create a new code execution context by calling the `createCodeContext()` method in JavaScript or `create_code_context()` in Python and passing the context parameters. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sandbox = await Sandbox.create() const context = await sandbox.createCodeContext({ cwd: '/home/user', language: 'python', requestTimeoutMs: 60_000, }) const result = await sandbox.runCode('print("Hello, world!")', { context }) console.log(result) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sandbox = Sandbox.create() context = sandbox.create_code_context( cwd='/home/user', language='python', request_timeout=60_000, ) result = sandbox.run_code('print("Hello, world!")', context=context) print(result) ``` ## List active Code Contexts You can list active code execution contexts by calling the `listCodeContexts()` method in JavaScript or `list_code_contexts()` in Python. This will return a list of active code execution contexts. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sandbox = await Sandbox.create() const contexts = await sandbox.listCodeContexts() console.log(contexts) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sandbox = Sandbox.create() contexts = sandbox.list_code_contexts() print(contexts) ``` ## Restart a Code Context You can restart an active code execution context by calling the `restartCodeContext()` method in JavaScript or `restart_code_context()` in Python and passing the context object or context ID. Restarting a context will clear its state and start a new code execution session in the same context. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sandbox = await Sandbox.create() const context = await sandbox.createCodeContext({ cwd: '/home/user', language: 'python', requestTimeoutMs: 60_000, }) // using context object await sandbox.restartCodeContext(context) // using context ID await sandbox.restartCodeContext(context.contextId) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sandbox = Sandbox.create() context = sandbox.create_code_context( cwd='/home/user', language='python', request_timeout=60_000, ) # using context object restarted_context = sandbox.restart_code_context(context) print(restarted_context) # using context ID restarted_context = sandbox.restart_code_context(context.contextId) print(restarted_context) ``` ## Remove a Code Context You can remove an active code execution context by calling the `removeCodeContext()` method in JavaScript or `remove_code_context()` in Python and passing the context object or context ID. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sandbox = await Sandbox.create() const context = await sandbox.createCodeContext({ cwd: '/home/user', language: 'python', requestTimeoutMs: 60_000, }) // using context object await sandbox.removeCodeContext(context) // using context ID await sandbox.removeCodeContext(context.contextId) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sandbox = Sandbox.create() context = sandbox.create_code_context( cwd='/home/user', language='python', request_timeout=60_000, ) # using context object sandbox.remove_code_context(context) # using context ID sandbox.remove_code_context(context.contextId) ``` # Create charts & visualizations Source: https://e2b.mintlify.app/docs/code-interpreting/create-charts-visualizations E2B Sandbox allows you to create charts and visualizations by executing Python code inside the sandbox with `runCode()` method in JavaScript and `run_code()` method in Python. These charts and visualizations can be [static](/docs/code-interpreting/create-charts-visualizations/static-charts) or [interactive](/docs/code-interpreting/create-charts-visualizations/interactive-charts) plots. # Interactive charts Source: https://e2b.mintlify.app/docs/code-interpreting/create-charts-visualizations/interactive-charts E2B also allows you to create interactive charts with custom styling. E2B automatically detects charts when executing Python code with `runCode()` in JavaScript or `run_code()` in Python. The Python code must include Matplotlib charts. When a chart is detected, E2B sends the data of the chart back to the client. You can access the chart in the `execution.results` array where each item is a `Result` object with the `chart` property. Try out [AI Data Analyst](https://github.com/e2b-dev/ai-analyst/) - a Next.js app that uses E2B to create interactive charts. Here's a simple example of bar chart: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox, BarChart } from '@e2b/code-interpreter' const code = ` import matplotlib.pyplot as plt # Prepare data authors = ['Author A', 'Author B', 'Author C', 'Author D'] sales = [100, 200, 300, 400] # Create and customize the bar chart plt.figure(figsize=(10, 6)) plt.bar(authors, sales, label='Books Sold', color='blue') plt.xlabel('Authors') plt.ylabel('Number of Books Sold') plt.title('Book Sales by Authors') # Display the chart plt.tight_layout() plt.show() ` const sandbox = await Sandbox.create() const result = await sandbox.runCode(code) const chart = result.results[0].chart as BarChart console.log('Type:', chart.type) console.log('Title:', chart.title) console.log('X Label:', chart.x_label) console.log('Y Label:', chart.y_label) console.log('X Unit:', chart.x_unit) console.log('Y Unit:', chart.y_unit) console.log('Elements:', chart.elements) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox code = """ import matplotlib.pyplot as plt # Prepare data authors = ['Author A', 'Author B', 'Author C', 'Author D'] sales = [100, 200, 300, 400] # Create and customize the bar char plt.figure(figsize=(10, 6)) plt.bar(authors, sales, label='Books Sold', color='blue') plt.xlabel('Authors') plt.ylabel('Number of Books Sold') plt.title('Book Sales by Authors') # Display the chart plt.tight_layout() plt.show() """ sandbox = Sandbox.create() execution = sandbox.run_code(code) chart = execution.results[0].chart print('Type:', chart.type) print('Title:', chart.title) print('X Label:', chart.x_label) print('Y Label:', chart.y_label) print('X Unit:', chart.x_unit) print('Y Unit:', chart.y_unit) print('Elements:') for element in chart.elements: print('\n Label:', element.label) print(' Value:', element.value) print(' Group:', element.group) ``` The code above will output the following: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Type: bar Title: Book Sales by Authors X Label: Authors Y Label: Number of Books Sold X Unit: null Y Unit: null Elements: [ { label: "Author A", group: "Books Sold", value: 100, }, { label: "Author B", group: "Books Sold", value: 200, }, { label: "Author C", group: "Books Sold", value: 300, }, { label: "Author D", group: "Books Sold", value: 400, } ] ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Type: ChartType.BAR Title: Book Sales by Authors X Label: Authors Y Label: Number of Books Sold X Unit: None Y Unit: None Elements: Label: Author A Value: 100.0 Group: Books Sold Label: Author B Value: 200.0 Group: Books Sold Label: Author C Value: 300.0 Group: Books Sold Label: Author D Value: 400.0 Group: Books Sold ``` You can send this data to your frontend to create an interactive chart with your favorite charting library. *** ## Supported intertactive charts The following charts are currently supported: * Line chart * Bar chart * Scatter plot * Pie chart * Box and whisker plot # Static charts Source: https://e2b.mintlify.app/docs/code-interpreting/create-charts-visualizations/static-charts Every time you run Python code with `runCode()` in JavaScript or `run_code()` method in Python, the code is executed in a headless Jupyter server inside the sandbox. E2B automatically detects any plots created with Matplotlib and sends them back to the client as images encoded in the base64 format. These images are directly accesible on the `result` items in the `execution.results` array. Here's how to retrieve a static chart from the executed Python code that contains a Matplotlib plot. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' import fs from 'fs' const codeToRun = ` import matplotlib.pyplot as plt plt.plot([1, 2, 3, 4]) plt.ylabel('some numbers') plt.show() ` const sandbox = await Sandbox.create() // Run the code inside the sandbox const execution = await sandbox.runCode(codeToRun) // There's only one result in this case - the plot displayed with `plt.show()` const firstResult = execution.results[0] if (firstResult.png) { // Save the png to a file. The png is in base64 format. fs.writeFileSync('chart.png', firstResult.png, { encoding: 'base64' }) console.log('Chart saved as chart.png') } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import base64 from e2b_code_interpreter import Sandbox code_to_run = """ import matplotlib.pyplot as plt plt.plot([1, 2, 3, 4]) plt.ylabel('some numbers') plt.show() """ sandbox = Sandbox.create() # Run the code inside the sandbox execution = sandbox.run_code(code_to_run) # There's only one result in this case - the plot displayed with `plt.show()` first_result = execution.results[0] if first_result.png: # Save the png to a file. The png is in base64 format. with open('chart.png', 'wb') as f: f.write(base64.b64decode(first_result.png)) print('Chart saved as chart.png') ``` The code in the variable `codeToRun`/`code_to_run` will produce this following plot that we're saving as `chart.png` file. # Customize the template Source: https://e2b.mintlify.app/docs/code-interpreting/customize-template Extend the Code Interpreter sandbox or build the production template from source The Code Interpreter SDK runs on the public `code-interpreter-v1` sandbox template. You can customize it in two ways: 1. [Create a custom template](#create-a-custom-template) that extends `code-interpreter-v1` with extra packages or a different runtime — this is the recommended approach for most use cases. 2. [Build the production template](#build-the-production-template) directly from the [`code-interpreter` repository](https://github.com/e2b-dev/code-interpreter) — useful when contributing to the SDK or building the official `code-interpreter-v1` template yourself. *** ## Create a custom template Use this when you need extra preinstalled packages or a different runtime. Your template builds on top of `code-interpreter-v1`, so everything the Code Interpreter SDK relies on stays in place. ### 1. Install the E2B SDK ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npm install e2b dotenv ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b python-dotenv ``` ### 2. Create a template file ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b'; export const template = Template().fromTemplate("code-interpreter-v1"); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = Template().from_template("code-interpreter-v1") ``` ### 3. Create a build script ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import "dotenv/config"; import { Template, defaultBuildLogger } from 'e2b'; import { template } from './template'; async function main() { await Template.build(template, 'code-interpreter-custom', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }); } main().catch(console.error); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from dotenv import load_dotenv from e2b import Template, default_build_logger from template import template load_dotenv() Template.build( template, alias="code-interpreter-custom", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` ### 4. Set your environment variables Create a `.env` file with your API key (loaded by `dotenv` / `load_dotenv()`): ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} E2B_API_KEY=e2b_*** ``` ### 5. Build the template ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` ### 6. Use the custom template ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create("code-interpreter-custom") const execution = await sbx.runCode("print('Hello, World!')") console.log(execution.logs.stdout) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create("code-interpreter-custom") execution = sbx.run_code("print('Hello, World!')") print(execution.logs.stdout) ``` See [Install custom packages](/docs/quickstart/install-custom-packages) for preinstalling specific Python and Node.js packages with `pip` / `npm`. *** ## Build the production template To build the official `code-interpreter-v1` template from the [`code-interpreter` repository](https://github.com/e2b-dev/code-interpreter), use `build_prod.py`. This is the script CI and releases run, and it's the path to take when you're contributing to the SDK or want to rebuild the production template yourself. ### 1. Clone the repository The template lives in the `template/` directory — run the remaining steps from there. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} git clone https://github.com/e2b-dev/code-interpreter.git cd code-interpreter/template ``` ### 2. Install the build dependencies ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install -r requirements-dev.txt ``` ### 3. Provide your credentials Create a `.env` file with your API key: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} E2B_API_KEY=e2b_*** ``` ### 4. Build the template ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build_prod.py ``` To force a clean rebuild that ignores the layer cache, set `SKIP_CACHE=true`: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} SKIP_CACHE=true python build_prod.py ``` # Streaming Source: https://e2b.mintlify.app/docs/code-interpreting/streaming E2B Code Interpreter SDK allows you to stream the output, and results when executing code in the sandbox. ## Stream `stdout` and `stderr` When using the `runCode()` method in JavaScript or `run_code()` in Python you can pass `onStdout`/`on_stdout` and `onStderr`/`on_stderr` callbacks to handle the output. ```js JavaScript & TypeScript highlight={15-17} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const codeToRun = ` import time import sys print("This goes first to stdout") time.sleep(3) print("This goes later to stderr", file=sys.stderr) time.sleep(3) print("This goes last") ` const sandbox = await Sandbox.create() sandbox.runCode(codeToRun, { // Use `onError` to handle runtime code errors onError: error => console.error('error:', error), onStdout: data => console.log('stdout:', data), onStderr: data => console.error('stderr:', data), }) ``` ```python Python highlight={17-19} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox code_to_run = """ import time import sys print("This goes first to stdout") time.sleep(3) print("This goes later to stderr", file=sys.stderr) time.sleep(3) print("This goes last") """ sandbox = Sandbox.create() sandbox.run_code( code_to_run, # Use `on_error` to handle runtime code errors on_error=lambda error: print('error:', error), on_stdout=lambda data: print('stdout:', data), on_stderr=lambda data: print('stderr:', data), ) ``` The code above will print the following: ```javascript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} stdout: { error: false, line: "This goes first to stdout\n", timestamp: 1729049666861000, } stderr: { error: true, line: "This goes later to stderr\n", timestamp: 1729049669924000, } stdout: { error: false, line: "This goes last\n", timestamp: 1729049672664000, } ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} stdout: This goes first to stdout stderr: This goes later to stderr stdout: This goes last ``` ## Stream `results` When using the `runCode()` method in JavaScript or `run_code()` in Python you can pass `onResults`/`on_results` callback to receive results from the sandbox like charts, tables, text, and more. ```js JavaScript & TypeScript highlight={20} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const codeToRun = ` import matplotlib.pyplot as plt # Prepare data categories = ['Category A', 'Category B', 'Category C', 'Category D'] values = [10, 20, 15, 25] # Create and customize the bar chart plt.figure(figsize=(10, 6)) plt.bar(categories, values, color='green') plt.xlabel('Categories') plt.ylabel('Values') plt.title('Values by Category') # Display the chart plt.show() ` const sandbox = await Sandbox.create() await sandbox.runCode(codeToRun, { onResult: result => console.log('result:', result), }) ``` ```python Python highlight={24} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox code_to_run = """ import matplotlib.pyplot as plt # Prepare data categories = ['Category A', 'Category B', 'Category C', 'Category D'] values = [10, 20, 15, 25] # Create and customize the bar chart plt.figure(figsize=(10, 6)) plt.bar(categories, values, color='green') plt.xlabel('Categories') plt.ylabel('Values') plt.title('Values by Category') # Display the chart plt.show() """ sandbox = Sandbox.create() sandbox.run_code( code_to_run, on_result=lambda result: print('result:', result), ) ``` # Supported languages Source: https://e2b.mintlify.app/docs/code-interpreting/supported-languages Typically you use Python to run AI-generated code for data analysis but you can use other languages as well. Out of the box E2B Sandbox supports: * [Python](/docs/code-interpreting/supported-languages/python) * [JavaScript and TypeScript](/docs/code-interpreting/supported-languages/javascript) * [R](/docs/code-interpreting/supported-languages/r) * [Java](/docs/code-interpreting/supported-languages/java) * [Bash](/docs/code-interpreting/supported-languages/bash) You can use any custom language runtime by creating a [custom sandbox template](/docs/template/quickstart). # Run bash code Source: https://e2b.mintlify.app/docs/code-interpreting/supported-languages/bash Use the `runCode`/`run_code` method to run bash code inside the sandbox. You'll need to pass the `language` parameter with value `bash`. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() const execution = await sbx.runCode('echo "Hello, world!"', { language: 'bash' }) console.log(execution) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create() execution = sbx.run_code("echo 'Hello, world!'", language="bash") print(execution) ``` # Run Java code Source: https://e2b.mintlify.app/docs/code-interpreting/supported-languages/java Use the `runCode`/`run_code` method to run Java code inside the sandbox. You'll need to pass the `language` parameter with value `java`. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() const execution = await sbx.runCode('System.out.println("Hello, world!");', { language: 'java' }) console.log(execution) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create() execution = sbx.run_code('System.out.println("Hello, world!");', language="java") print(execution) ``` # Run JavaScript and TypeScript code Source: https://e2b.mintlify.app/docs/code-interpreting/supported-languages/javascript Use the `runCode`/`run_code` method to run JavaScript and TypeScript code inside the sandbox. You'll need to pass the `language` parameter with value `javascript` or `js` for JavaScript and `typescript` or `ts` for TypeScript. The E2B Code Interpreter supports TypeScript, top-level await, ESM-style imports and automatic promises resolution. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter'; // Create a new sandbox const sbx = await Sandbox.create(); // Install the axios package await sbx.commands.run("npm install axios"); // Run the code const execution = await sbx.runCode(` import axios from "axios"; const url: string = "https://api.github.com/status"; const response = await axios.get(url); response.data; `, { language: "ts" } ); console.log(execution); // Execution { // results: [], // logs: { // stdout: [ "{ message: 'GitHub lives! (2025-05-28 10:49:55 -0700) (1)' }\n" ], // stderr: [], // }, // error: undefined, // executionCount: 1, // text: [Getter], // toJSON: [Function: toJSON], // } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox # Create a new sandbox sbx = Sandbox.create() # Install the axios package sbx.commands.run("npm install axios") # Run the code execution = sbx.run_code(""" import axios from "axios"; const url: string = "https://api.github.com/status"; const response = await axios.get(url); response.data; """, language="ts", ) print(execution) # Execution( # Results: [ # Result({ message: 'GitHub lives! (2025-05-28 10:48:47 -0700) (1)' }) # ], # Logs: Logs(stdout: [], stderr: []), # Error: None # ) ``` # Run Python code Source: https://e2b.mintlify.app/docs/code-interpreting/supported-languages/python Use the `runCode`/`run_code` method to run Python code inside the sandbox. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() const execution = await sbx.runCode('print("Hello, world!")') console.log(execution) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create() execution = sbx.run_code('print("Hello, world!")') print(execution) ``` # Run R code Source: https://e2b.mintlify.app/docs/code-interpreting/supported-languages/r Use the `runCode`/`run_code` method to run R code inside the sandbox. You'll need to pass the `language` parameter with value `r`. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() const execution = await sbx.runCode('print("Hello, world!")', { language: 'r' }) console.log(execution) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create() execution = sbx.run_code('print("Hello, world!")', language="r") print(execution) ``` # Running commands in sandbox Source: https://e2b.mintlify.app/docs/commands You can run terminal commands inside the sandbox using the `commands.run()` method. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const result = await sandbox.commands.run('ls -l') console.log(result) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() result = sandbox.commands.run('ls -l') print(result) ``` # Cookbook Source: https://e2b.mintlify.app/docs/cookbook # Filesystem Source: https://e2b.mintlify.app/docs/filesystem Each E2B Sandbox has its own isolated filesystem. The [Hobby tier](https://e2b.dev/pricing) sandboxes come with 10 GB of the free disk space and [Pro tier](https://e2b.dev/pricing) sandboxes come with 20 GB. With E2B SDK you can: * [Read and write files to the sandbox.](/docs/filesystem/read-write) * [Attach custom metadata to files.](/docs/filesystem/metadata) * [Watch directory for changes.](/docs/filesystem/watch) * [Upload data to the sandbox.](/docs/filesystem/upload) * [Download data from the sandbox.](/docs/filesystem/download) # Download data from sandbox Source: https://e2b.mintlify.app/docs/filesystem/download You can download data from the sandbox using the `files.read()` method. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import fs from 'fs' import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Read file from sandbox const content = await sandbox.files.read('/path/in/sandbox') // Write file to local filesystem fs.writeFileSync('/local/path', content) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Read file from sandbox content = sandbox.files.read('/path/in/sandbox') # Write file to local filesystem with open('/local/path', 'w') as file: file.write(content) ``` ## Download with pre-signed URL Sometimes, you may want to let users from unauthorized environments, like a browser, download files from the sandbox. For this use case, you can use pre-signed URLs to let users download files securely. All you need to do is create a sandbox with the `secure: true` option. A download URL will then be generated with a signature that allows only authorized users to access files. You can optionally set an expiration time for the URL so that it will be valid only for a limited time. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import fs from 'fs' import { Sandbox } from 'e2b' // Start a secured sandbox (all operations must be authorized by default) const sandbox = await Sandbox.create(template, { secure: true }) // Create a pre-signed URL for file download with a 10 second expiration const publicUrl = await sandbox.downloadUrl( 'demo.txt', { useSignatureExpiration: 10_000, // optional }, ) // Download a file with a pre-signed URL (this can be used in any environment, such as a browser) const res = await fetch(publicUrl) const content = await res.text() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Start a secured sandbox (all operations must be authorized by default) sandbox = Sandbox.create(timeout=12_000, secure=True) # Create a pre-signed URL for file download with a 10 second expiration # The user only has to visit the URL to download the file, this also works in a browser. signed_url = sbx.download_url(path="demo.txt", user="user", use_signature_expiration=10_000) ``` # Get information about a file or directory Source: https://e2b.mintlify.app/docs/filesystem/info You can get information about a file or directory using the `files.getInfo()` / `files.get_info()` methods. Information such as file name, type, and path is returned. Files can also carry custom user-defined metadata, which is returned in the `metadata` field — see [Custom metadata](/docs/filesystem/metadata). ### Getting information about a file ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Create a new file await sandbox.files.write('test_file.txt', 'Hello, world!') // Get information about the file const info = await sandbox.files.getInfo('test_file.txt') console.log(info) // { // name: 'test_file.txt', // type: 'file', // path: '/home/user/test_file.txt', // size: 13, // mode: 0o644, // permissions: '-rw-r--r--', // owner: 'user', // group: 'user', // modifiedTime: '2025-05-26T12:00:00.000Z', // symlinkTarget: null // } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Create a new file sandbox.files.write('test_file', 'Hello, world!') # Get information about the file info = sandbox.files.get_info('test_file') print(info) # EntryInfo( # name='test_file.txt', # type=, # path='/home/user/test_file.txt', # size=13, # mode=0o644, # permissions='-rw-r--r--', # owner='user', # group='user', # modified_time='2025-05-26T12:00:00.000Z', # symlink_target=None # ) ``` ### Getting information about a directory ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Create a new directory await sandbox.files.makeDir('test_dir') // Get information about the directory const info = await sandbox.files.getInfo('test_dir') console.log(info) // { // name: 'test_dir', // type: 'dir', // path: '/home/user/test_dir', // size: 0, // mode: 0o755, // permissions: 'drwxr-xr-x', // owner: 'user', // group: 'user', // modifiedTime: '2025-05-26T12:00:00.000Z', // symlinkTarget: null // } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Create a new directory sandbox.files.make_dir('test_dir') # Get information about the directory info = sandbox.files.get_info('test_dir') print(info) # EntryInfo( # name='test_dir', # type=, # path='/home/user/test_dir', # size=0, # mode=0o755, # permissions='drwxr-xr-x', # owner='user', # group='user', # modified_time='2025-05-26T12:00:00.000Z', # symlink_target=None # ) ``` # Attach custom metadata to files Source: https://e2b.mintlify.app/docs/filesystem/metadata Attach custom key-value metadata to files when writing them to the sandbox and read it back with file info methods. You can attach custom key-value metadata to files when writing them to the sandbox using the `metadata` option. The metadata is persisted with the file and returned by `files.getInfo()` / `files.get_info()`, `files.list()`, and `files.rename()`. ## Prerequisites Custom file metadata requires templates with envd version `v0.6.2` or above. If you are using a custom template created before envd `v0.6.2`, you need to rebuild it. You can check the template envd version using the `e2b template list` command or by viewing the templates list on the dashboard. ## Writing a file with metadata ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const info = await sandbox.files.write('report.txt', 'hello', { metadata: { author: 'alice', purpose: 'demo' }, }) console.log(info.metadata) // { author: 'alice', purpose: 'demo' } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() info = sandbox.files.write("report.txt", "hello", metadata={"author": "alice", "purpose": "demo"}) print(info.metadata) # {"author": "alice", "purpose": "demo"} ``` ## Writing multiple files with metadata When writing multiple files, the same metadata is applied to every file in the upload. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() await sandbox.files.writeFiles( [ { path: 'a.txt', data: 'A' }, { path: 'b.txt', data: 'B' }, ], { metadata: { source: 'import' } } ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() sandbox.files.write_files( [ { "path": "a.txt", "data": "A" }, { "path": "b.txt", "data": "B" }, ], metadata={"source": "import"}, ) ``` ## Reading metadata back ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() await sandbox.files.write('report.txt', 'hello', { metadata: { author: 'alice' }, }) const info = await sandbox.files.getInfo('report.txt') console.log(info.metadata) // { author: 'alice' } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() sandbox.files.write("report.txt", "hello", metadata={"author": "alice"}) info = sandbox.files.get_info("report.txt") print(info.metadata) # {"author": "alice"} ``` ## Limitations * Metadata keys and values must be printable US-ASCII characters. * Keys are lowercased by the sandbox, so they may differ in case when read back. * Overwriting a file replaces its metadata — metadata from the previous write is not preserved. * Metadata is stored as `user.e2b.*` extended attributes on the file inside the sandbox, so you can also read or set it from within the sandbox using standard tools like `getfattr` and `setfattr`. # Read & write files Source: https://e2b.mintlify.app/docs/filesystem/read-write ## Reading files You can read files from the sandbox filesystem using the `files.read()` method. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const fileContent = await sandbox.files.read('/path/to/file') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() file_content = sandbox.files.read('/path/to/file') ``` ## Writing single files You can write single files to the sandbox filesystem using the `files.write()` method. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() await sandbox.files.write('/path/to/file', 'file content') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() sandbox.files.write('/path/to/file', 'file content') ``` ## Writing multiple files You can also write multiple files to the sandbox. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() await sandbox.files.write([ { path: '/path/to/a', data: 'file content' }, { path: '/another/path/to/b', data: 'file content' } ]) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() sandbox.files.write_files([ { "path": "/path/to/a", "data": "file content" }, { "path": "another/path/to/b", "data": "file content" } ]) ``` # Upload data to sandbox Source: https://e2b.mintlify.app/docs/filesystem/upload You can upload data to the sandbox using the `files.write()` method. ## Upload single file ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import fs from 'fs' import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Read file from local filesystem const content = fs.readFileSync('/local/path') // Upload file to sandbox await sandbox.files.write('/path/in/sandbox', content) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Read file from local filesystem with open("path/to/local/file", "rb") as file: # Upload file to sandbox sandbox.files.write("/path/in/sandbox", file) ``` ## Upload with pre-signed URL Sometimes, you may want to let users from unauthorized environments, like a browser, upload files to the sandbox. For this use case, you can use pre-signed URLs to let users upload files securely. All you need to do is create a sandbox with the `secure: true` option. An upload URL will then be generated with a signature that allows only authorized users to upload files. You can optionally set an expiration time for the URL so that it will be valid only for a limited time. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Start a secured sandbox (all operations must be authorized by default) const sandbox = await Sandbox.create(template, { secure: true }) // Create a pre-signed URL for file upload with a 10 second expiration const publicUploadUrl = await sandbox.uploadUrl( 'demo.txt', { useSignatureExpiration: 10_000, // optional }, ) // Upload a file with a pre-signed URL (this can be used in any environment, such as a browser) const form = new FormData() form.append('file', 'file content') await fetch(publicUploadUrl, { method: 'POST', body: form }) // File is now available in the sandbox and you can read it const content = sandbox.files.read('/path/in/sandbox') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox import requests # Start a secured sandbox (all operations must be authorized by default) sandbox = Sandbox.create(timeout=12_000, secure=True) # Create a pre-signed URL for file upload with a 10 second expiration signed_url = sandbox.upload_url(path="demo.txt", user="user", use_signature_expiration=10_000) form_data = {"file":"file content"} requests.post(signed_url, data=form_data) # File is now available in the sandbox and you can read it content = sandbox.files.read('/path/in/sandbox') ``` ## Upload directory / multiple files ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Read all files in the directory and store their paths and contents in an array const readDirectoryFiles = (directoryPath) => { // Read all files in the local directory const files = fs.readdirSync(directoryPath); // Map files to objects with path and data const filesArray = files .filter(file => { const fullPath = path.join(directoryPath, file); // Skip if it's a directory return fs.statSync(fullPath).isFile(); }) .map(file => { const filePath = path.join(directoryPath, file); // Read the content of each file return { path: filePath, data: fs.readFileSync(filePath, 'utf8') }; }); return filesArray; }; // Usage example const files = readDirectoryFiles('/local/dir'); console.log(files); // [ // { path: '/local/dir/file1.txt', data: 'File 1 contents...' }, // { path: '/local/dir/file2.txt', data: 'File 2 contents...' }, // ... // ] await sandbox.files.write(files) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os from e2b import Sandbox sandbox = Sandbox.create() def read_directory_files(directory_path): files = [] # Iterate through all files in the directory for filename in os.listdir(directory_path): file_path = os.path.join(directory_path, filename) # Skip if it's a directory if os.path.isfile(file_path): # Read file contents in binary mode with open(file_path, "rb") as file: files.append({ 'path': file_path, 'data': file.read() }) return files files = read_directory_files("/local/dir") print(files) # [ # {"path": "/local/dir/file1.txt", "data": "File 1 contents..." }, # { "path": "/local/dir/file2.txt", "data": "File 2 contents..." }, # ... # ] sandbox.files.write_files(files) ``` # Watch sandbox directory for changes Source: https://e2b.mintlify.app/docs/filesystem/watch You can watch a directory for changes using the `files.watchDir()` method in JavaScript and `files.watch_dir()` method in Python. Since events are tracked asynchronously, their delivery may be delayed. It's recommended not to collect or close watcher immediately after making a change. ```js JavaScript & TypeScript highlight={7-12} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox, FilesystemEventType } from 'e2b' const sandbox = await Sandbox.create() const dirname = '/home/user' // Start watching directory for changes const handle = await sandbox.files.watchDir(dirname, async (event) => { console.log(event) if (event.type === FilesystemEventType.WRITE) { console.log(`wrote to file ${event.name}`) } }) // Trigger file write event await sandbox.files.write(`${dirname}/my-file`, 'hello') ``` ```python Python highlight={7,12-16} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, FilesystemEventType sandbox = Sandbox.create() dirname = '/home/user' # Watch directory for changes handle = sandbox.files.watch_dir(dirname) # Trigger file write event sandbox.files.write(f"{dirname}/my-file", "hello") # Retrieve the latest new events since the last `get_new_events()` call events = handle.get_new_events() for event in events: print(event) if event.type == FilesystemEventType.WRITE: print(f"wrote to file {event.name}") ``` ## Recursive watching You can enable recursive watching using the parameter `recursive`. When rapidly creating new folders (e.g., deeply nested path of folders), events other than `CREATE` might not be emitted. To avoid this behavior, create the required folder structure in advance. ```js JavaScript & TypeScript highlight={13,17} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox, FilesystemEventType } from 'e2b' const sandbox = await Sandbox.create() const dirname = '/home/user' // Start watching directory for changes const handle = await sandbox.files.watchDir(dirname, async (event) => { console.log(event) if (event.type === FilesystemEventType.WRITE) { console.log(`wrote to file ${event.name}`) } }, { recursive: true }) // Trigger file write event await sandbox.files.write(`${dirname}/my-folder/my-file`, 'hello') ``` ```python Python highlight={7,9} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, FilesystemEventType sandbox = Sandbox.create() dirname = '/home/user' # Watch directory for changes handle = sandbox.files.watch_dir(dirname, recursive=True) # Trigger file write event sandbox.files.write(f"{dirname}/my-folder/my-file", "hello") # Retrieve the latest new events since the last `get_new_events()` call events = handle.get_new_events() for event in events: print(event) if event.type == FilesystemEventType.WRITE: print(f"wrote to file {event.name}") ``` ## Including entry information You can include information about the affected file or directory in each event using the parameter `includeEntry` in JavaScript and `include_entry` in Python. When enabled, each event carries the entry's information — such as its path, type, and size — in the `entry` field. Entry information may be unset for remove events since the path no longer exists. Including entry information requires templates with envd version `v0.6.3` or above — using the `includeEntry` / `include_entry` option on older sandboxes will throw an error. ```js JavaScript & TypeScript highlight={10} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const dirname = '/home/user' // Start watching directory for changes const handle = await sandbox.files.watchDir(dirname, async (event) => { console.log(event.type, event.name, event.entry?.path, event.entry?.type) }, { includeEntry: true }) // Trigger file write event await sandbox.files.write(`${dirname}/my-file`, 'hello') ``` ```python Python highlight={7,14} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() dirname = '/home/user' # Watch directory for changes handle = sandbox.files.watch_dir(dirname, include_entry=True) # Trigger file write event sandbox.files.write(f"{dirname}/my-file", "hello") # Retrieve the latest new events since the last `get_new_events()` call events = handle.get_new_events() for event in events: print(event.type, event.name, event.entry.path if event.entry else None) ``` ## Watching network filesystem mounts By default, watching paths on network filesystem mounts (NFS, CIFS, SMB, FUSE) is rejected. You can explicitly opt in using the parameter `allowNetworkMounts` in JavaScript and `allow_network_mounts` in Python. Events on network mounts may be unreliable or not delivered at all, which is why the explicit opt-in is required. Changes made by another client outside of the sandbox (for example, a different machine writing to the same network share) are **not** propagated to the watcher — only changes made from within the sandbox are detected. Watching network mounts requires templates with envd version `v0.6.4` or above — using the `allowNetworkMounts` / `allow_network_mounts` option on older sandboxes will throw an error. ```js JavaScript & TypeScript highlight={10} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const dirname = '/mnt/nfs-share/my-dir' // Start watching a directory on a network mount for changes const handle = await sandbox.files.watchDir(dirname, async (event) => { console.log(event.type, event.name) }, { allowNetworkMounts: true }) // Trigger file write event await sandbox.files.write(`${dirname}/my-file`, 'hello') ``` ```python Python highlight={7,14} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() dirname = '/mnt/nfs-share/my-dir' # Watch a directory on a network mount for changes handle = sandbox.files.watch_dir(dirname, allow_network_mounts=True) # Trigger file write event sandbox.files.write(f"{dirname}/my-file", "hello") # Retrieve the latest new events since the last `get_new_events()` call events = handle.get_new_events() for event in events: print(event.type, event.name) ``` # Custom domain Source: https://e2b.mintlify.app/docs/network/custom-domain How to set up a custom domain for Sandboxes hosted on E2B. We will set up a GCP VM running Caddy server with Docker and Cloudflare DNS to proxy the requests to the Sandboxes. Example: `8080-sandboxid.mydomain.com` -> `8080-sandboxid.e2b.app` ### Prerequisites * Domain name registered and configured with Cloudflare DNS. * Cloudflare API Token that allows you to manage DNS records. ### GCP VM setup 1. Create a VM instance by running the following command: Replace `your-project-id` with your actual project ID. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} gcloud compute instances create e2b-custom-domain-proxy \ --project=your-project-id \ --zone=us-west1-a \ --machine-type=n2-standard-2 \ --can-ip-forward \ --tags=http-server,https-server \ --image-project=debian-cloud \ --image-family=debian-12 \ --boot-disk-size=20GB ``` 2. After the VM is created, you can connect to it using the following command: Replace `your-project-id` with your actual project ID. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # May take a few seconds until the instance is ready to accept SSH connections gcloud compute ssh e2b-custom-domain-proxy \ --project=your-project-id \ --zone=us-west1-a ``` ### Server setup 1. Install the latest stable version of Docker: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} curl -fsSL https://get.docker.com | sudo sh ``` 2. Create a Dockerfile that will be used to build the Caddy server image with Cloudflare DNS: ```Dockerfile Dockerfile theme={"theme":{"light":"github-light","dark":"github-dark-default"}} FROM caddy:builder AS builder RUN xcaddy build \ --with github.com/caddy-dns/cloudflare FROM caddy:latest COPY --from=builder /usr/bin/caddy /usr/bin/caddy ``` 3. Create a Docker Compose file that will be used to start the Caddy server: ```yaml docker-compose.yml theme={"theme":{"light":"github-light","dark":"github-dark-default"}} services: caddy: build: context: . dockerfile: Dockerfile container_name: caddy-proxy restart: unless-stopped ports: - "80:80" - "443:443" - "443:443/udp" # Optional: HTTP/3 volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config environment: - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN} networks: - caddy_network volumes: caddy_data: caddy_config: networks: caddy_network: driver: bridge ``` 4. Create a Caddyfile for proxying the requests to the Sandboxes: Replace `*.mydomain.com` with your actual wildcard domain name. ```Caddyfile Caddyfile theme={"theme":{"light":"github-light","dark":"github-dark-default"}} *.mydomain.com { # Use Cloudflare DNS for ACME challenge tls { dns cloudflare {env.CLOUDFLARE_API_TOKEN} } # Capture sandboxId for reuse # {labels.N} splits the host by "." and indexes from right to left: # e.g., for "abc123.mydomain.com": # {labels.0} = "com" (TLD) # {labels.1} = "mydomain" (domain) # {labels.2} = "abc123" (subdomain) vars sandboxId {labels.2} # Reverse proxy to corresponding e2b.app Sandbox reverse_proxy {vars.sandboxId}.e2b.app:443 { # Set the Host header to the e2b.app domain header_up Host {vars.sandboxId}.e2b.app # Forward real IP header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} header_up X-Forwarded-Host {host} # Use HTTPS to upstream transport http { tls tls_server_name {vars.sandboxId}.e2b.app } } # Optional: Add logging log { output file /var/log/caddy/access.log format json } } ``` 5. Create a .env file that will be used to store the Cloudflare API Token: ```env .env theme={"theme":{"light":"github-light","dark":"github-dark-default"}} CLOUDFLARE_API_TOKEN=your-cloudflare-api-token ``` 6. Build and start the Caddy server: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} docker compose build docker compose up -d ``` ### Domain setup Log into the Cloudflare dashboard and create a new A wildcard DNS record pointing to the IP address of the GCP VM. Replace `GCP_VM_IP` with the IP address of the GCP VM. It may take a few minutes for the DNS record to propagate and for the certificate to be issued. If you have existing AAAA (IPv6) records for this domain name, make sure they are either removed or updated to point to the GCP VM. ```txt DNS theme={"theme":{"light":"github-light","dark":"github-dark-default"}} *.mydomain.com A GCP_VM_IP ``` ### Testing the setup We will create a new Sandbox on E2B and install a simple HTTP server in it. 1. Create a new Sandbox using [E2B CLI](/docs/cli): ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sandbox create base ``` 2. Install and run a simple HTTP server in the sandbox: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sudo apt install nginx sudo systemctl start nginx ``` 3. Visit the sandbox URL in your browser: `https://80-sandboxid.mydomain.com`. You should see the default nginx welcome page. # Internet access Source: https://e2b.mintlify.app/docs/network/internet-access Every sandbox has outbound access to the internet by default. You can control and restrict this access to fit security-sensitive workloads, from a simple on/off switch to fine-grained allow and deny lists. To expose services running inside the sandbox to the outside world, see [Sandbox public URL](/docs/network/public-url). ## Controlling internet access You can control whether a sandbox has access to the internet by using the `allowInternetAccess` / `allow_internet_access` parameter when creating a sandbox. By default, internet access is enabled, but you can disable it for security-sensitive workloads. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with internet access enabled (default) const sandbox = await Sandbox.create({ allowInternetAccess: true }) // Create sandbox without internet access const isolatedSandbox = await Sandbox.create({ allowInternetAccess: false }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create sandbox with internet access enabled (default) sandbox = Sandbox.create(allow_internet_access=True) # Create sandbox without internet access isolated_sandbox = Sandbox.create(allow_internet_access=False) ``` When internet access is disabled, the sandbox cannot make outbound network connections, which provides an additional layer of security for sensitive code execution. Setting `allowInternetAccess` / `allow_internet_access` to a falsy value is equivalent to setting `network.denyOut` / `network.deny_out` to `['0.0.0.0/0']` (denying all traffic). ## Fine-grained network control For more granular control over network access, you can use the network configuration option to specify allow and deny lists for outbound traffic. ### Allow and deny lists You can specify IP addresses, CIDR blocks, or domain names that the sandbox is allowed to use: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Deny all traffic except specific IPs const sandbox = await Sandbox.create({ network: { denyOut: ({ allTraffic }) => [allTraffic], // allTraffic === '0.0.0.0/0' allowOut: ['1.1.1.1', '8.8.8.0/24'] } }) // Deny specific IPs only const restrictedSandbox = await Sandbox.create({ network: { denyOut: ['8.8.8.8'] } }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Deny all traffic except specific IPs sandbox = Sandbox.create( network={ "deny_out": lambda ctx: [ctx.all_traffic], # ctx.all_traffic == "0.0.0.0/0" "allow_out": ["1.1.1.1", "8.8.8.0/24"] } ) # Deny specific IPs only restricted_sandbox = Sandbox.create( network={ "deny_out": ["8.8.8.8"] } ) ``` The selector callback (`({ allTraffic }) => [allTraffic]` / `lambda ctx: [ctx.all_traffic]`) is the recommended way to express "all traffic" (`0.0.0.0/0`). The `ALL_TRAFFIC` constant remains exported for backward compatibility. ### Domain-based filtering You can allow traffic to specific domains by specifying hostnames in `allowOut` / `allow_out`. When using domain-based filtering, you must deny all other traffic in `denyOut` / `deny_out`. Domains are not supported in the deny lists. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Allow only traffic to google.com const sandbox = await Sandbox.create({ network: { allowOut: ['google.com'], denyOut: ({ allTraffic }) => [allTraffic] } }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Allow only traffic to google.com sandbox = Sandbox.create( network={ "allow_out": ["google.com"], "deny_out": lambda ctx: [ctx.all_traffic] } ) ``` When any domain is used, the default nameserver `8.8.8.8` is automatically allowed to ensure proper DNS resolution. You can also use wildcards to allow all subdomains of a domain: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Allow traffic to any subdomain of mydomain.com const sandbox = await Sandbox.create({ network: { allowOut: ['*.mydomain.com'], denyOut: ({ allTraffic }) => [allTraffic] } }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Allow traffic to any subdomain of mydomain.com sandbox = Sandbox.create( network={ "allow_out": ["*.mydomain.com"], "deny_out": lambda ctx: [ctx.all_traffic] } ) ``` You can combine domain names with IP addresses and CIDR blocks: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Allow traffic to specific domains and IPs const sandbox = await Sandbox.create({ network: { allowOut: ['api.example.com', '*.github.com', '8.8.8.8'], denyOut: ({ allTraffic }) => [allTraffic] } }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Allow traffic to specific domains and IPs sandbox = Sandbox.create( network={ "allow_out": ["api.example.com", "*.github.com", "8.8.8.8"], "deny_out": lambda ctx: [ctx.all_traffic] } ) ``` Domain-based filtering only works for HTTP traffic on port 80 (via Host header inspection) and TLS traffic on port 443 (via SNI inspection). Traffic on other ports uses CIDR-based filtering only. UDP-based protocols like QUIC/HTTP3 are not supported for domain filtering. ### Behavior of blocked TCP connections Due to firewall design, blocked connections may appear successful from inside the sandbox. The firewall has to accept the connection first before it can decide whether the destination is allowed. This means that, from inside the sandbox, a TCP connection can succeed and report the socket as open even when the destination is denied - no packets actually reach the destination. To verify that traffic is reaching its destination, check for an application-level response (e.g. an HTTP status code, a TLS handshake, or expected protocol bytes) rather than relying on the TCP connection succeeding. This is a limitation of how outbound traffic is currently routed from the sandbox to our firewall and may change in the future. ### Priority rules When both allow and deny rules are specified, **allow rules always take precedence** over deny rules. This means if an IP address is in both lists, it will be allowed. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Even though all traffic is denied, 1.1.1.1 and 8.8.8.8 are explicitly allowed const sandbox = await Sandbox.create({ network: { denyOut: ({ allTraffic }) => [allTraffic], allowOut: ['1.1.1.1', '8.8.8.8'] } }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Even though all traffic is denied, 1.1.1.1 and 8.8.8.8 are explicitly allowed sandbox = Sandbox.create( network={ "deny_out": lambda ctx: [ctx.all_traffic], "allow_out": ["1.1.1.1", "8.8.8.8"] } ) ``` ### Per-host request transforms Per-host request transforms are currently in public beta. You can start using them right away, no need to request access. Please keep in mind that this feature is still in active development, and the features and ways of interacting with it might change during this period. If you have any questions or feedback, please [reach out to us](https://e2b.dev/dashboard?support=true). Any input is greatly appreciated. You can register per-host rules under `network.rules` to apply transforms (for example, inject HTTP headers) on outbound requests matching a host. Rules are keyed by host and registering one does **not** grant egress on its own — the host must still be referenced via `allowOut` / `allow_out`. The `transform.headers` object is sent on the wire as-is and injected by the egress proxy on matching HTTP/HTTPS requests. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' await Sandbox.create({ network: { // Only allow egress to hosts that have rules registered. allowOut: ({ rules }) => [...rules.keys()], // Deny all other traffic denyOut: ({ allTraffic }) => [allTraffic], // Register per-host rules rules: { 'api.example.com': [ { transform: { headers: { 'X-Header': 'Content' }, }, }, ], }, }, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create( network={ # Only allow egress to hosts that have rules registered. "allow_out": lambda ctx: list(ctx.rules.keys()), # Deny all other traffic "deny_out": lambda ctx: [ctx.all_traffic], # Register per-host rules "rules": { "api.example.com": [ { "transform": { "headers": {"X-Header": "Content"}, }, }, ], }, }, ) ``` In JavaScript, `network.rules` accepts either a plain object or a `Map`: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const rules = new Map([ ['api.example.com', [{ transform: { headers: { 'X-Trace': 'on' } } }]], ]) await Sandbox.create({ network: { allowOut: ({ rules }) => [...rules.keys()], rules }, }) ``` ### Updating network settings on a running sandbox You can update the network configuration of an already running sandbox using `updateNetwork` (JavaScript) or `update_network` (Python). This replaces the current egress rules with the provided configuration without restarting the sandbox. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Tighten egress on the running sandbox: block 8.8.8.8 await sandbox.updateNetwork({ denyOut: ['8.8.8.8'], }) // Replace with an allow-list only await sandbox.updateNetwork({ denyOut: ({ allTraffic }) => [allTraffic], allowOut: ['api.example.com'], }) // Toggle internet access without recreating the sandbox await sandbox.updateNetwork({ allowInternetAccess: false }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Tighten egress on the running sandbox: block 8.8.8.8 sandbox.update_network({"deny_out": ["8.8.8.8"]}) # Replace with an allow-list only sandbox.update_network({ "deny_out": lambda ctx: [ctx.all_traffic], "allow_out": ["api.example.com"], }) # Toggle internet access without recreating the sandbox sandbox.update_network({"allow_internet_access": False}) ``` `updateNetwork` / `update_network` **replaces** the current egress configuration — it does not merge with the existing rules. Calling it with an empty object (`updateNetwork({})` / `update_network({})`) clears all allow and deny rules set at create time. Create-only options such as `allowPublicTraffic` / `allow_public_traffic`, `maskRequestHost` / `mask_request_host` and network rules in `network.rules` cannot be changed after the sandbox is created. # Proxy tunneling Source: https://e2b.mintlify.app/docs/network/ip-tunneling How to tunnel Sandbox network traffic through a proxy server ## Setting up proxy tunneling We will set up a proxy server on a GCP VM instance running [Shadowsocks](https://shadowsocks.org) and use it to tunnel the Sandbox network traffic. This will allow you to use a dedicated IP address for outgoing requests. ### GCP VM setup 1. Create a firewall rule to allow all tcp/udp traffic to port 8388. Replace `your-project-id` with your actual project ID. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} gcloud compute firewall-rules create shadowsocks \ --project=your-project-id \ --direction=INGRESS \ --priority=1000 \ --network=default \ --action=ALLOW \ --rules=tcp:8388,udp:8388 \ --source-ranges=0.0.0.0/0 \ --target-tags=allow-shadowsocks ``` 2. Create a VM instance with the following tags: `allow-shadowsocks`. Replace `your-project-id` with your actual project ID. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} gcloud compute instances create shadowsocks-vm \ --project=your-project-id \ --zone=us-west1-a \ --machine-type=n2-standard-2 \ --can-ip-forward \ --tags=allow-shadowsocks \ --image-project=debian-cloud \ --image-family=debian-12 \ --boot-disk-size=20GB ``` 3. After the VM is created, you can connect to it using the following command: Replace `your-project-id` with your actual project ID. ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # May take a few seconds until the instance is ready to accept SSH connections gcloud compute ssh shadowsocks-vm \ --project=your-project-id \ --zone=us-west1-a ``` ### Shadowsocks server setup (VM) SSH into the VM and follow the instructions below to install and configure Shadowsocks. 1. Install the necessary packages, if missing: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sudo apt update sudo apt install -y wget tar ``` 2. Download and install Shadowsocks (v1.24.0): ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} wget https://github.com/shadowsocks/shadowsocks-rust/releases/latest/download/shadowsocks-v1.24.0.x86_64-unknown-linux-gnu.tar.xz tar -xf shadowsocks-*.tar.xz sudo mv ssserver ssmanager ssurl /usr/local/bin/ ``` 3. Create a shadowsocks configuration file: Replace `STRONG_PASSWORD_HERE` with your own password. ```json /etc/shadowsocks/server.json theme={"theme":{"light":"github-light","dark":"github-dark-default"}} { "server": "0.0.0.0", "server_port": 8388, "password": "STRONG_PASSWORD_HERE", "method": "aes-256-gcm", "mode": "tcp_and_udp", "timeout": 300, "fast_open": true } ``` 4. Enable IP forwarding: ```txt /etc/sysctl.d/99-shadowsocks.conf theme={"theme":{"light":"github-light","dark":"github-dark-default"}} net.core.default_qdisc=fq net.ipv4.tcp_congestion_control=bbr net.ipv4.ip_forward=1 ``` ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sudo sysctl -p /etc/sysctl.d/99-shadowsocks.conf ``` Optional: Update the Ubuntu Firewall rules to allow traffic to port 8388: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sudo ufw allow 8388/tcp sudo ufw allow 8388/udp ``` 5. Start the Shadowsocks server: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sudo ssserver -c /etc/shadowsocks/server.json -v ``` You should see the following in the console output: ``` shadowsocks tcp server listening on 0.0.0.0:8388 shadowsocks udp server listening on 0.0.0.0:8388 ``` 6. Optional: Create a systemd service to start the Shadowsocks server on boot: ```ini /etc/systemd/system/ssserver.service theme={"theme":{"light":"github-light","dark":"github-dark-default"}} [Unit] Description=Shadowsocks Rust Server After=network.target [Service] ExecStart=/usr/local/bin/ssserver -c /etc/shadowsocks/server.json Restart=always LimitNOFILE=51200 User=root [Install] WantedBy=multi-user.target ``` Reload the systemd daemon and start the service: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sudo systemctl daemon-reload sudo systemctl enable --now ssserver ``` You can check the status of the service with the following command: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sudo systemctl status ssserver ``` ### Shadowsocks client setup (sandbox) Create a custom Sandbox template that uses the shadowsocks client to tunnel TCP traffic through the proxy server we set up above. Route only designated traffic through the proxy. 1. Create a configuration file for the shadowsocks client: Replace `SERVER_IP` with the IP address of the proxy server and `STRONG_PASSWORD_HERE` with your own password. ```json config.json theme={"theme":{"light":"github-light","dark":"github-dark-default"}} { "server": "SERVER_IP", "server_port": 8388, "password": "STRONG_PASSWORD_HERE", "method": "aes-256-gcm", "local_address": "0.0.0.0", "local_port": 1080, "mode": "tcp" } ``` 2. Create a template file (`template.ts` / `template.py`): ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, waitForPort } from "e2b"; const shadowSocksVersion = "1.24.0" const downloadUrl = `https://github.com/shadowsocks/shadowsocks-rust/releases/latest/download/shadowsocks-v${shadowSocksVersion}.x86_64-unknown-linux-gnu.tar.xz` export const template = Template() .fromBaseImage() .runCmd([ `wget ${downloadUrl}`, "tar -xf shadowsocks-*.tar.xz", "sudo mv sslocal /usr/local/bin/" ]) .copy('config.json', 'config.json') .setStartCmd( "sudo sslocal -c config.json --daemonize", waitForPort(1080) ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, wait_for_port shadowsocks_version = "1.24.0" download_url = f"https://github.com/shadowsocks/shadowsocks-rust/releases/latest/download/shadowsocks-v{shadowsocks_version}.x86_64-unknown-linux-gnu.tar.xz" template = ( Template() .from_base_image() .run_cmd([ f"wget {download_url}", "tar -xf shadowsocks-*.tar.xz", "sudo mv sslocal /usr/local/bin/" ]) .copy("config.json", "config.json") .set_start_cmd( "sudo sslocal -c config.json --daemonize", wait_for_port(1080) ) ) ``` 3. Create a build script (`build.ts` / `build.py`): ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, defaultBuildLogger } from "e2b"; import { template as localProxyTemplate } from "./template"; await Template.build(template, { alias: 'shadowsocks-client', memoryMB: 1024, cpuCount: 1, onBuildLogs: defaultBuildLogger() }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, default_build_logger from .template import template Template.build( template, alias="shadowsocks-client", memory_mb=1024, cpu_count=1, on_build_logs=default_build_logger() ) ``` 4. Build the template using the build script: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` Route all traffic through the proxy. 1. Create a configuration file for the shadowsocks client: Replace `SERVER_IP` with the IP address of the proxy server and `STRONG_PASSWORD_HERE` with your own password. ```json config.json theme={"theme":{"light":"github-light","dark":"github-dark-default"}} { "server": "SERVER_IP", "server_port": 8388, "password": "STRONG_PASSWORD_HERE", "method": "aes-256-gcm", "local_address": "0.0.0.0", "local_port": 1080, "mode": "tcp" } ``` 2. Create a script for setting up the iptables rules: Replace `SERVER_IP` with the IP address of the proxy server. ```bash iptables-rules.sh theme={"theme":{"light":"github-light","dark":"github-dark-default"}} iptables -t nat -N SHADOWSOCKS iptables -t nat -A SHADOWSOCKS -d SERVER_IP -j RETURN iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 12345 iptables -t nat -A OUTPUT -p tcp -j SHADOWSOCKS ``` 3. Create a template file (`template.ts` / `template.py`): ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, waitForPort } from "e2b"; const shadowsocksVersion = "1.24.0" const downloadUrl = `https://github.com/shadowsocks/shadowsocks-rust/releases/latest/download/shadowsocks-v${shadowsocksVersion}.x86_64-unknown-linux-gnu.tar.xz` export const template = Template() .fromBaseImage() .aptInstall("iptables") .runCmd([ `wget ${downloadUrl}`, "tar -xf shadowsocks-*.tar.xz", "sudo mv sslocal /usr/local/bin/" ]) .copy('config.json', 'config.json') .copy('iptables-rules.sh', 'iptables-rules.sh', { mode: 0o755 }) .setStartCmd( "sudo sslocal -c config.json --protocol redir -b 0.0.0.0:12345 --daemonize && sudo iptables-rules.sh", waitForPort(1080) ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, wait_for_port shadowsocks_version = "1.24.0" download_url = f"https://github.com/shadowsocks/shadowsocks-rust/releases/latest/download/shadowsocks-v{shadowsocks_version}.x86_64-unknown-linux-gnu.tar.xz" template = ( Template() .from_base_image() .apt_install("iptables") .run_cmd([ f"wget {download_url}", "tar -xf shadowsocks-*.tar.xz", "sudo mv sslocal /usr/local/bin/" ]) .copy("config.json", "config.json") .copy("iptables-rules.sh", "iptables-rules.sh", mode=0o755) .set_start_cmd( "sudo sslocal -c config.json --protocol redir -b 0.0.0.0:12345 --daemonize && sudo iptables-rules.sh", wait_for_port(1080) ) ) ``` 4. Create a build script (`build.ts` / `build.py`): ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, defaultBuildLogger } from "e2b"; import { template as localProxyTemplate } from "./template"; await Template.build(template, { alias: 'shadowsocks-client', memoryMB: 1024, cpuCount: 1, onBuildLogs: defaultBuildLogger() }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, default_build_logger from .template import template Template.build( template, alias="shadowsocks-client", memory_mb=1024, cpu_count=1, on_build_logs=default_build_logger() ) ``` 5. Build the template using the build script: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.py ``` ## Using the proxies Create a new Sandbox from the built template and run a curl command to verify that the traffic is routed through the proxy: Only designated traffic should be routed through the proxy. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from "e2b"; // create a new Sandbox from the built template const sbx = await Sandbox.create('shadowsocks-client') // run a command to curl the IP address of the proxy server const curl = await sbx.commands.run('curl --socks5 127.0.0.1:1080 https://ifconfig.me') console.log(curl.stdout) await sbx.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # create a new Sandbox from the built template sbx = Sandbox.create('shadowsocks-client') # run a command to curl the IP address of the proxy server curl = sbx.commands.run('curl --socks5 127.0.0.1:1080 https://ifconfig.me') print(curl.stdout) sbx.kill() ``` ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx sandbox.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python sandbox.py ``` You should see the IP address of the proxy server. All traffic should be routed through the proxy. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from "e2b"; // create a new Sandbox from the built template const sbx = await Sandbox.create('shadowsocks-client') // run a command to curl the IP address of the proxy server const curl = await sbx.commands.run('curl https://ifconfig.me') console.log(curl.stdout) await sbx.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # create a new Sandbox from the built template sbx = Sandbox.create('shadowsocks-client') # run a command to curl the IP address of the proxy server curl = sbx.commands.run('curl https://ifconfig.me') print(curl.stdout) sbx.kill() ``` ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx sandbox.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python sandbox.py ``` You should see the IP address of the proxy server. # Sandbox public URL Source: https://e2b.mintlify.app/docs/network/public-url Every sandbox can be reached by a public URL, which lets you expose and connect to services running inside it. This page covers how to get that URL, connect to a server inside the sandbox, and customize the `Host` header services receive. To control the sandbox's outbound internet access, see [Internet access](/docs/network/internet-access). To require authentication on the public URL, see [Restricting public access](/docs/network/restrict-public-access). ## Sandbox public URL Every sandbox has a public URL that can be used to access running services inside the sandbox. ```js JavaScript & TypeScript highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // You need to always pass a port number to get the host const host = sandbox.getHost(3000) console.log(`https://${host}`) ``` ```python Python highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # You need to always pass a port number to get the host host = sandbox.get_host(3000) print(f'https://{host}') ``` The code above will print something like this: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} https://3000-i62mff4ahtrdfdkyn2esc.e2b.app ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} https://3000-i62mff4ahtrdfdkyn2esc.e2b.app ``` The first leftmost part of the host is the port number we passed to the method. ## Connecting to a server running inside the sandbox You can start a server inside the sandbox and connect to it using the approach above. In this example we will start a simple HTTP server that listens on port 3000 and responds with the content of the directory where the server is started. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Start a simple HTTP server inside the sandbox. const process = await sandbox.commands.run('python -m http.server 3000', { background: true }) const host = sandbox.getHost(3000) const url = `https://${host}` console.log('Server started at:', url) // Fetch data from the server inside the sandbox. const response = await fetch(url); const data = await response.text(); console.log('Response from server inside sandbox:', data); // Kill the server process inside the sandbox. await process.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests from e2b import Sandbox sandbox = Sandbox.create() # Start a simple HTTP server inside the sandbox. process = sandbox.commands.run("python -m http.server 3000", background=True) host = sandbox.get_host(3000) url = f"https://{host}" print('Server started at:', url) # Fetch data from the server inside the sandbox. response = requests.get(url) data = response.text print('Response from server inside sandbox:', data) # Kill the server process inside the sandbox. process.kill() ``` This output will look like this: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Server started at: https://3000-ip3nfrvajtqu5ktoxugc7.e2b.app Response from server inside sandbox: Directory listing for /

Directory listing for /



``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Server started at: https://3000-ip3nfrvajtqu5ktoxugc7.e2b.app Response from server inside sandbox: Directory listing for /

Directory listing for /



```
## Masking request host headers You can customize the `Host` header that gets sent to services running inside the sandbox using the `maskRequestHost` / `mask_request_host` option. This is useful when your application expects a specific host format. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with custom host masking const sandbox = await Sandbox.create({ network: { maskRequestHost: 'localhost:${PORT}' } }) // The ${PORT} variable will be replaced with the actual port number // Requests to the sandbox will have Host header set to for example: localhost:8080 ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create sandbox with custom host masking sandbox = Sandbox.create( network={ "mask_request_host": "localhost:${PORT}" } ) # The ${PORT} variable will be replaced with the actual port number # Requests to the sandbox will have Host header set to for example: localhost:8080 ``` The `${PORT}` variable in the mask will be automatically replaced with the actual port number of the requested service. # Restricting public access Source: https://e2b.mintlify.app/docs/network/restrict-public-access By default, a sandbox's [public URL](/docs/network/public-url) is reachable by anyone who knows it. For sensitive workloads, you can require callers to authenticate with a per-sandbox token before any request reaches the services inside. ## Restricting public access to sandbox URLs By default, sandbox URLs are publicly accessible. You can restrict access to require authentication using the `allowPublicTraffic` / `allow_public_traffic` option: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with restricted public access const sandbox = await Sandbox.create({ network: { allowPublicTraffic: false } }) // The sandbox has a traffic access token console.log(sandbox.trafficAccessToken) // Start a server inside the sandbox await sandbox.commands.run('python -m http.server 8080', { background: true }) const host = sandbox.getHost(8080) const url = `https://${host}` // Request without token will fail with 403 const response1 = await fetch(url) console.log(response1.status) // 403 // Request with token will succeed const response2 = await fetch(url, { headers: { 'e2b-traffic-access-token': sandbox.trafficAccessToken } }) console.log(response2.status) // 200 ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests from e2b import Sandbox # Create sandbox with restricted public access sandbox = Sandbox.create( network={ "allow_public_traffic": False } ) # The sandbox has a traffic access token print(sandbox.traffic_access_token) # Start a server inside the sandbox sandbox.commands.run("python -m http.server 8080", background=True) host = sandbox.get_host(8080) url = f"https://{host}" # Request without token will fail with 403 response1 = requests.get(url) print(response1.status_code) # 403 # Request with token will succeed response2 = requests.get(url, headers={ 'e2b-traffic-access-token': sandbox.traffic_access_token }) print(response2.status_code) # 200 ``` When `allowPublicTraffic` / `allow_public_traffic` is set to a falsy value, all requests to the sandbox's public URLs must include the `e2b-traffic-access-token` header with the value from `sandbox.trafficAccessToken` / `sandbox.traffic_access_token`. # Running your first Sandbox Source: https://e2b.mintlify.app/docs/quickstart This guide will show you how to start your first E2B Sandbox. Every new E2B account get \$100 in credits. You can sign up [here](https://e2b.dev/auth/sign-up). 1. Navigate to the E2B Dashboard. 2. Copy your [API key](https://e2b.dev/dashboard?tab=keys). 3. Paste your E2B API key into your .env file. ```bash .env theme={"theme":{"light":"github-light","dark":"github-dark-default"}} E2B_API_KEY=e2b_*** ``` Install the E2B SDK to your project by running the following command in your terminal. ```javascript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npm i @e2b/code-interpreter dotenv ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b-code-interpreter python-dotenv ``` We'll write the minimal code for starting Sandbox, executing Python inside it and listing all files inside the root directory. ```javascript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // index.ts import 'dotenv/config' import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() // Creates a persistent sandbox session const execution = await sbx.runCode('print("hello world")') // Execute Python inside the sandbox console.log(execution.logs) const files = await sbx.files.list('/') console.log(files) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # main.py from dotenv import load_dotenv load_dotenv() from e2b_code_interpreter import Sandbox sbx = Sandbox.create() # Creates a persistent sandbox session execution = sbx.run_code("print('hello world')") # Execute Python inside the sandbox print(execution.logs) files = sbx.files.list("/") print(files) ``` Run the code with the following command: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx ./index.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python main.py ``` # Connect LLMs to E2B Source: https://e2b.mintlify.app/docs/quickstart/connect-llms E2B can work with any LLM and AI framework. The easiest way to connect an LLM to E2B is to use the tool use capabilities of the LLM (sometimes known as function calling). If the LLM doesn't support tool use, you can, for example, prompt the LLM to output code snippets and then manually extract the code snippets with [RegEx](https://en.wikipedia.org/wiki/Regular_expression). ## Contents * [OpenAI](#openai) * [Anthropic](#anthropic) * [Mistral](#mistral) * [Groq](#groq) * [Vercel AI SDK](#vercel-ai-sdk) * [CrewAI](#crewai) * [LangChain](#langchain) * [LlamaIndex](#llamaindex) * [Ollama](#ollama) * [Hugging Face](#hugging-face) *** ## OpenAI ### Simple ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install openai e2b-code-interpreter from openai import OpenAI from e2b_code_interpreter import Sandbox # Create OpenAI client client = OpenAI() system = "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." prompt = "Calculate how many r's are in the word 'strawberry'" # Send messages to OpenAI API response = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": system}, {"role": "user", "content": prompt} ] ) # Extract the code from the response code = response.choices[0].message.content # Execute code in E2B Sandbox if code: with Sandbox.create() as sandbox: execution = sandbox.run_code(code) result = execution.text print(result) ``` ### Function calling ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install openai e2b-code-interpreter import json from openai import OpenAI from e2b_code_interpreter import Sandbox # Create OpenAI client client = OpenAI() model = "gpt-4o" # Define the messages messages = [ { "role": "user", "content": "Calculate how many r's are in the word 'strawberry'" } ] # Define the tools tools = [{ "type": "function", "function": { "name": "execute_python", "description": "Execute python code in a Jupyter notebook cell and return result", "parameters": { "type": "object", "properties": { "code": { "type": "string", "description": "The python code to execute in a single cell" } }, "required": ["code"] } } }] # Generate text with OpenAI response = client.chat.completions.create( model=model, messages=messages, tools=tools, ) # Append the response message to the messages list response_message = response.choices[0].message messages.append(response_message) # Execute the tool if it's called by the model if response_message.tool_calls: for tool_call in response_message.tool_calls: if tool_call.function.name == "execute_python": # Create a sandbox and execute the code with Sandbox.create() as sandbox: code = json.loads(tool_call.function.arguments)['code'] execution = sandbox.run_code(code) result = execution.text # Send the result back to the model messages.append({ "role": "tool", "name": "execute_python", "content": result, "tool_call_id": tool_call.id, }) # Generate the final response final_response = client.chat.completions.create( model=model, messages=messages ) print(final_response.choices[0].message.content) ``` *** ## Anthropic ### Simple ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install anthropic e2b-code-interpreter from anthropic import Anthropic from e2b_code_interpreter import Sandbox # Create Anthropic client anthropic = Anthropic() system_prompt = "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." prompt = "Calculate how many r's are in the word 'strawberry'" # Send messages to Anthropic API response = anthropic.messages.create( model="claude-3-5-sonnet-20240620", max_tokens=1024, messages=[ {"role": "assistant", "content": system_prompt}, {"role": "user", "content": prompt} ] ) # Extract code from response code = response.content[0].text # Execute code in E2B Sandbox with Sandbox.create() as sandbox: execution = sandbox.run_code(code) result = execution.logs.stdout print(result) ``` ### Function calling ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install anthropic e2b-code-interpreter from anthropic import Anthropic from e2b_code_interpreter import Sandbox # Create Anthropic client client = Anthropic() model = "claude-3-5-sonnet-20240620" # Define the messages messages = [ { "role": "user", "content": "Calculate how many r's are in the word 'strawberry'" } ] # Define the tools tools = [{ "name": "execute_python", "description": "Execute python code in a Jupyter notebook cell and return (not print) the result", "input_schema": { "type": "object", "properties": { "code": { "type": "string", "description": "The python code to execute in a single cell" } }, "required": ["code"] } }] # Generate text with Anthropic message = client.messages.create( model=model, max_tokens=1024, messages=messages, tools=tools ) # Append the response message to the messages list messages.append({ "role": "assistant", "content": message.content }) # Execute the tool if it's called by the model if message.stop_reason == "tool_use": tool_use = next(block for block in message.content if block.type == "tool_use") tool_name = tool_use.name tool_input = tool_use.input if tool_name == "execute_python": with Sandbox.create() as sandbox: code = tool_input['code'] execution = sandbox.run_code(code) result = execution.text # Append the tool result to the messages list messages.append({ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": tool_use.id, "content": result, } ], }) # Generate the final response final_response = client.messages.create( model=model, max_tokens=1024, messages=messages, tools=tools ) print(final_response.content[0].text) ``` *** ## Mistral ### Simple ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install mistralai e2b-code-interpreter import os from mistralai import Mistral from e2b_code_interpreter import Sandbox # Create Mistral client client = Mistral(api_key=os.environ["MISTRAL_API_KEY"]) system_prompt = "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." prompt = "Calculate how many r's are in the word 'strawberry'" # Send the prompt to the model response = client.chat.complete( model="codestral-latest", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} ] ) # Extract the code from the response code = response.choices[0].message.content # Execute code in E2B Sandbox with Sandbox.create() as sandbox: execution = sandbox.run_code(code) result = execution.text print(result) ``` ### Function calling ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install mistralai e2b-code-interpreter import os import json from mistralai import Mistral from e2b_code_interpreter import Sandbox # Create Mistral client client = Mistral(api_key=os.environ["MISTRAL_API_KEY"]) model = "mistral-large-latest" messages = [ { "role": "user", "content": "Calculate how many r's are in the word 'strawberry'" } ] # Define the tools tools = [{ "type": "function", "function": { "name": "execute_python", "description": "Execute python code in a Jupyter notebook cell and return result", "parameters": { "type": "object", "properties": { "code": { "type": "string", "description": "The python code to execute in a single cell" } }, "required": ["code"] } } }] # Send the prompt to the model response = client.chat.complete( model=model, messages=messages, tools=tools ) # Append the response message to the messages list response_message = response.choices[0].message messages.append(response_message) # Execute the tool if it's called by the model if response_message.tool_calls: for tool_call in response_message.tool_calls: if tool_call.function.name == "execute_python": # Create a sandbox and execute the code with Sandbox.create() as sandbox: code = json.loads(tool_call.function.arguments)['code'] execution = sandbox.run_code(code) result = execution.text # Send the result back to the model messages.append({ "role": "tool", "name": "execute_python", "content": result, "tool_call_id": tool_call.id, }) # Generate the final response final_response = client.chat.complete( model=model, messages=messages, ) print(final_response.choices[0].message.content) ``` *** ## Groq ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install groq e2b-code-interpreter import os from groq import Groq from e2b_code_interpreter import Sandbox api_key = os.environ["GROQ_API_KEY"] # Create Groq client client = Groq(api_key=api_key) system_prompt = "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." prompt = "Calculate how many r's are in the word 'strawberry.'" # Send the prompt to the model response = client.chat.completions.create( model="llama3-70b-8192", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}, ] ) # Extract the code from the response code = response.choices[0].message.content # Execute code in E2B Sandbox with Sandbox.create() as sandbox: execution = sandbox.run_code(code) result = execution.text print(result) ``` *** ## Vercel AI SDK Vercel's [AI SDK](https://sdk.vercel.ai) offers support for multiple different LLM providers through a unified JavaScript interface that's easy to use. ### Simple ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // npm install ai @ai-sdk/openai @e2b/code-interpreter import { openai } from '@ai-sdk/openai' import { generateText } from 'ai' import { Sandbox } from '@e2b/code-interpreter' // Create OpenAI client const model = openai('gpt-4o') const system = "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." const prompt = "Calculate how many r's are in the word 'strawberry'" // Generate code with OpenAI const { text: code } = await generateText({ model, system, prompt }) // Run the code in E2B Sandbox const sandbox = await Sandbox.create() const { text, results, logs, error } = await sandbox.runCode(code) console.log(text) ``` ### Function calling ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // npm install ai @ai-sdk/openai zod @e2b/code-interpreter import { openai } from '@ai-sdk/openai' import { generateText } from 'ai' import z from 'zod' import { Sandbox } from '@e2b/code-interpreter' // Create OpenAI client const model = openai('gpt-4o') const prompt = "Calculate how many r's are in the word 'strawberry'" // Generate text with OpenAI const { text } = await generateText({ model, prompt, tools: { // Define a tool that runs code in a sandbox execute_python: { description: 'Execute python code in a Jupyter notebook cell and return result', parameters: z.object({ code: z.string().describe('The python code to execute in a single cell'), }), execute: async ({ code }) => { // Create a sandbox, execute LLM-generated code, and return the result const sandbox = await Sandbox.create() const { text, results, logs, error } = await sandbox.runCode(code) return results }, }, }, // This is required to feed the tool call result back to the LLM maxSteps: 2 }) console.log(text) ``` *** ## CrewAI [CrewAI](https://crewai.com/) is a platform for building AI agents. ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install crewai e2b-code-interpreter from crewai.tools import tool from crewai import Agent, Task, Crew, LLM from e2b_code_interpreter import Sandbox # Update tool definition using the decorator @tool("Python Interpreter") def execute_python(code: str) -> str: """ Execute Python code and return the results. """ with Sandbox.create() as sandbox: execution = sandbox.run_code(code) return execution.text # Define the agent python_executor = Agent( role='Python Executor', goal='Execute Python code and return the results', backstory='You are an expert Python programmer capable of executing code and returning results.', tools=[execute_python], llm=LLM(model="gpt-4o") ) # Define the task execute_task = Task( description="Calculate how many r's are in the word 'strawberry'", agent=python_executor, expected_output="The number of r's in the word 'strawberry'" ) # Create the crew code_execution_crew = Crew( agents=[python_executor], tasks=[execute_task], verbose=True, ) # Run the crew result = code_execution_crew.kickoff() print(result) ``` *** ## LangChain [LangChain](https://langchain.com/) offers support multiple different LLM providers. ### Simple ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install langchain langchain-openai e2b-code-interpreter from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from e2b_code_interpreter import Sandbox system_prompt = "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." prompt = "Calculate how many r's are in the word 'strawberry'" # Create LangChain components llm = ChatOpenAI(model="gpt-4o") prompt_template = ChatPromptTemplate.from_messages([ ("system", system_prompt), ("human", "{input}") ]) output_parser = StrOutputParser() # Create the chain chain = prompt_template | llm | output_parser # Run the chain code = chain.invoke({"input": prompt}) # Execute code in E2B Sandbox with Sandbox.create() as sandbox: execution = sandbox.run_code(code) result = execution.text print(result) ``` ### Agent ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install langchain langchain-openai e2b-code-interpreter from langchain_core.prompts import ChatPromptTemplate from langchain_core.tools import tool from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_openai import ChatOpenAI from e2b_code_interpreter import Sandbox system_prompt = "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." prompt = "Calculate how many r's are in the word 'strawberry'" # Define the tool @tool def execute_python(code: str): """ Execute python code in a Jupyter notebook. """ with Sandbox.create() as sandbox: execution = sandbox.run_code(code) return execution.text # Define LangChain components prompt_template = ChatPromptTemplate.from_messages([ ("system", system_prompt), ("human", "{input}"), ("placeholder", "{agent_scratchpad}"), ]) tools = [execute_python] llm = ChatOpenAI(model="gpt-4o", temperature=0) agent = create_tool_calling_agent(llm, tools, prompt_template) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # Run the agent agent_executor.invoke({"input": prompt}) ``` ### Function calling ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install langchain langchain-openai e2b-code-interpreter from langchain_openai import ChatOpenAI from langchain.tools import Tool from langchain.schema import HumanMessage, AIMessage, FunctionMessage from e2b_code_interpreter import Sandbox def execute_python(code: str): with Sandbox.create() as sandbox: execution = sandbox.run_code(code) return execution.text # Define a tool that uses the E2B Sandbox e2b_sandbox_tool = Tool( name="execute_python", func=execute_python, description="Execute python code in a Jupyter notebook cell and return result" ) # Initialize the language model and bind the tool llm = ChatOpenAI(model="gpt-4o").bind_tools([e2b_sandbox_tool]) # Define the messages messages = [ HumanMessage(content="Calculate how many 'r's are in the word 'strawberry'.") ] # Run the model with a prompt result = llm.invoke(messages) messages.append(AIMessage(content=result.content)) # Check if the model called the tool if result.additional_kwargs.get('tool_calls'): tool_call = result.additional_kwargs['tool_calls'][0] if tool_call['function']['name'] == "execute_python": code = tool_call['function']['arguments'] execution_result = execute_python(code) # Send the result back to the model messages.append( FunctionMessage(name="execute_python", content=execution_result) ) final_result = llm.invoke(messages) print(final_result.content) ``` *** ## LlamaIndex [LlamaIndex](https://www.llamaindex.ai/) offers support multiple different LLM providers. ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install llama-index e2b-code-interpreter from llama_index.core.tools import FunctionTool from llama_index.llms.openai import OpenAI from llama_index.core.agent import ReActAgent from e2b_code_interpreter import Sandbox # Define the tool def execute_python(code: str): with Sandbox.create() as sandbox: execution = sandbox.run_code(code) return execution.text e2b_sandbox_tool = FunctionTool.from_defaults( name="execute_python", description="Execute python code in a Jupyter notebook cell and return result", fn=execute_python ) # Initialize LLM llm = OpenAI(model="gpt-4o") # Initialize ReAct agent agent = ReActAgent.from_tools([e2b_sandbox_tool], llm=llm, verbose=True) agent.chat("Calculate how many r's are in the word 'strawberry'") ``` ## Ollama ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # pip install ollama import ollama from e2b_code_interpreter import Sandbox # Send the prompt to the model response = ollama.chat( model="llama3.2", messages=[{ "role": "system", "content": "You are a helpful assistant that can execute python code in a Jupyter notebook. Only respond with the code to be executed and nothing else. Strip backticks in code blocks." }, { "role": "user", "content": "Calculate how many r's are in the word 'strawberry'" } ]) # Extract the code from the response code = response['message']['content'] # Execute code in E2B Sandbox with Sandbox.create() as sandbox: execution = sandbox.run_code(code) result = execution.logs.stdout print(result) ``` *** ## Hugging Face Hugging Face offers support for serverless inference for models on their model hub with [Hugging Face's Inference API](https://huggingface.co/docs/inference-providers/en/index). Note that not every model on Hugging Face has native support for tool use and function calling. ````python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from huggingface_hub import InferenceClient from e2b_code_interpreter import Sandbox import re # Not all models are capable of direct tools use - we need to extract the code block manually and prompting the LLM to generate the code. def match_code_block(llm_response): pattern = re.compile(r'```python Python\n(.*?)\n```', re.DOTALL) # Match everything in between ```python and ``` match = pattern.search(llm_response) if match: code = match.group(1) print(code) return code return "" system_prompt = """You are a helpful coding assistant that can execute python code in a Jupyter notebook. You are given tasks to complete and you run Python code to solve them. Generally, you follow these rules: - ALWAYS FORMAT YOUR RESPONSE IN MARKDOWN - ALWAYS RESPOND ONLY WITH CODE IN CODE BLOCK LIKE THIS: \`\`\`python {code} \`\`\` """ prompt = "Calculate how many r's are in the word 'strawberry.'" # Initialize the client client = InferenceClient( provider="hf-inference", api_key="HF_INFERENCE_API_KEY" ) completion = client.chat.completions.create( model="Qwen/Qwen3-235B-A22B", # Or use any other model from Hugging Face messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}, ] ) content = completion.choices[0].message.content code = match_code_block(content) with Sandbox.create() as sandbox: execution = sandbox.run_code(code) print(execution) ```` # Install custom packages Source: https://e2b.mintlify.app/docs/quickstart/install-custom-packages There are two ways to install custom packages in the E2B Sandbox. 1. [Create custom sandbox with preinstalled packages](#create-a-custom-sandbox). 2. [Install packages during the sandbox runtime](#install-packages-during-the-sandbox-runtime). *** ## Create a custom sandbox Use this option if you know beforehand what packages you need in the sandbox. Sandbox templates allow you to define custom sandboxes with preinstalled packages and configurations. ### 1. Install the E2B SDK ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npm install e2b dotenv ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b python-dotenv ``` Create a `.env` file with your API key: ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} E2B_API_KEY=e2b_*** ``` ### 2. Create a template file Define your custom template with the packages you need. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b'; export const template = Template() .fromTemplate("code-interpreter-v1") .pipInstall(['cowsay']) // Install Python packages .npmInstall(['cowsay']); // Install Node.js packages ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = ( Template() .from_template("code-interpreter-v1") .pip_install(['cowsay']) # Install Python packages .npm_install(['cowsay']) # Install Node.js packages ) ``` ### 3. Create a build script ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.prod.ts import "dotenv/config"; import { Template, defaultBuildLogger } from 'e2b'; import { template } from './template'; async function main() { await Template.build(template, 'custom-packages', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }); } main().catch(console.error); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.prod.py from dotenv import load_dotenv from e2b import Template, default_build_logger from template import template load_dotenv() if __name__ == '__main__': Template.build( template, 'custom-packages', cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` ### 4. Build the template Run the build script to create your custom template: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.prod.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build.prod.py ``` This will build your template and you'll see build logs in the console. ### 5. Use your custom sandbox Now you can create sandboxes from your custom template: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create("custom-packages") // Your packages are already installed and ready to use ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create("custom-packages") # Your packages are already installed and ready to use ``` *** ## Install packages during the sandbox runtime Use this option if don't know beforehand what packages you need in the sandbox. You can install packages with the package manager of your choice. The packages installed during the runtime are available only in the running sandbox instance. When you start a new sandbox instance, the packages are not be available. ### 1. Install Python packages with PIP ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() sbx.commands.run('pip install cowsay') // This will install the cowsay package sbx.runCode(` import cowsay cowsay.cow("Hello, world!") `) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create() sbx.commands.run("pip install cowsay") # This will install the cowsay package sbx.run_code(""" import cowsay cowsay.cow("Hello, world!") """) ``` ### 2. Install Node.js packages with NPM ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() sbx.commands.run('npm install cowsay') // This will install the cowsay package sbx.runCode(` const cowsay = require('cowsay') console.log(cowsay.say({ text: 'Hello, world!' })) `, { language: 'javascript' }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create() sbx.commands.run("npm install cowsay") # This will install the cowsay package sbx.run_code(""" import { say } from 'cowsay' console.log(say('Hello, world!')) """, language="javascript") ``` ### 3. Install packages with package manager of your choice Since E2B Sandboxes are Debian based machines, you can use any package manager supported by Debian. You just need to make sure that the package manager is already installed in the sandbox. For example, to install `curl` and `git`, you can use the following commands: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from '@e2b/code-interpreter' const sbx = await Sandbox.create() await sbx.commands.run('apt-get update && apt-get install -y curl git') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b_code_interpreter import Sandbox sbx = Sandbox.create() sbx.commands.run("apt-get update && apt-get install -y curl git") ``` # Upload & downloads files Source: https://e2b.mintlify.app/docs/quickstart/upload-download-files E2B Sandbox allows you to upload and downloads file to and from the Sandbox. An alternative way to get your data to the sandbox is to create a [custom sandbox template](/docs/template/quickstart). ## Upload file ```ts JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Read local file relative to the current working directory const content = fs.readFileSync('local/file') const sbx = await Sandbox.create() // Upload file to the sandbox to absolute path '/home/user/my-file' await sbx.files.write('/home/user/my-file', content) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() # Read local file relative to the current working directory with open("local/file", "rb") as file: # Upload file to the sandbox to absolute path '/home/user/my-file' sbx.files.write("/home/user/my-file", file) ``` ## Upload multiple files Currently, if you want to upload multiple files, you need to upload each one of the separately. We're working on a better solution. ```ts JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Read local file relative to the current working directory const fileA = fs.readFileSync('local/file/a') const fileB = fs.readFileSync('local/file/b') const sbx = await Sandbox.create() // Upload file A to the sandbox to absolute path '/home/user/my-file-a' await sbx.files.write('/home/user/my-file-a', fileA) // Upload file B to the sandbox to absolute path '/home/user/my-file-b' await sbx.files.write('/home/user/my-file-b', fileB) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() # Read local file relative to the current working directory with open("local/file/a", "rb") as file: # Upload file to the sandbox to absolute path '/home/user/my-file-a' sbx.files.write("/home/user/my-file-a", file) with open("local/file/b", "rb") as file: # Upload file to the sandbox to absolute path '/home/user/my-file-b' sbx.files.write("/home/user/my-file-b", file) ``` ## Upload directory We currently don't support an easy way to upload a whole directory. You need to upload each file separately. We're working on a better solution. *** ## Download file To download a file, you need to first get the file's content and then write it to a local file. ```ts JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() // Download file from the sandbox to absolute path '/home/user/my-file' const content = await sbx.files.read('/home/user/my-file') // Write file to local path relative to the current working directory fs.writeFileSync('local/file', content) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() # Download file from the sandbox to absolute path '/home/user/my-file' content = sbx.files.read('/home/user/my-file') # Write file to local path relative to the current working directory with open('local/file', 'w') as file: file.write(content) ``` ## Download multiple files To download multiple files, you need to download each one of them separately from the sandbox. We're working on a better solution. ```ts JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() // Download file A from the sandbox by absolute path '/home/user/my-file-a' const contentA = await sbx.files.read('/home/user/my-file-a') // Write file A to local path relative to the current working directory fs.writeFileSync('local/file/a', contentA) // Download file B from the sandbox by absolute path '/home/user/my-file-b' const contentB = await sbx.files.read('/home/user/my-file-b') // Write file B to local path relative to the current working directory fs.writeFileSync('local/file/b', contentB) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() # Download file A from the sandbox by absolute path '/home/user/my-file-a' contentA = sbx.files.read('/home/user/my-file-a') # Write file A to local path relative to the current working directory with open('local/file/a', 'w') as file: file.write(contentA) # Download file B from the sandbox by absolute path '/home/user/my-file-b' contentB = sbx.files.read('/home/user/my-file-b') # Write file B to local path relative to the current working directory with open('local/file/b', 'w') as file: file.write(contentB) ``` ## Download directory We currently don't support an easy way to download a whole directory. You need to download each file separately. We're working on a better solution. # Sandbox lifecycle Source: https://e2b.mintlify.app/docs/sandbox Sandboxes stay running as long as you need them. When their timeout expires, they can automatically pause to save resources — preserving their full state so you can resume at any time. You can also configure an explicit timeout or shut down a sandbox manually. Sandboxes can run continuously for up to 24 hours (Pro) or 1 hour (Base). For longer workloads, use [pause and resume](/docs/sandbox/persistence) — pausing resets the runtime window, and your sandbox's full state is preserved indefinitely. ```js JavaScript & TypeScript highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with and keep it running for 60 seconds. // 🚨 Note: The units are milliseconds. const sandbox = await Sandbox.create({ timeoutMs: 60_000, }) ``` ```python Python highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create sandbox with and keep it running for 60 seconds. # 🚨 Note: The units are seconds. sandbox = Sandbox.create( timeout=60, ) ``` ## Change sandbox timeout during runtime You can change the sandbox timeout when it's running by calling the `setTimeout` method in JavaScript or `set_timeout` method in Python. When you call the set timeout method, the sandbox timeout will be reset to the new value that you specified. This can be useful if you want to extend the sandbox lifetime when it's already running. You can for example start with a sandbox with 1 minute timeout and then periodically call set timeout every time user interacts with it in your app. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with and keep it running for 60 seconds. const sandbox = await Sandbox.create({ timeoutMs: 60_000 }) // Change the sandbox timeout to 30 seconds. // 🚨 The new timeout will be 30 seconds from now. await sandbox.setTimeout(30_000) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create sandbox with and keep it running for 60 seconds. sandbox = Sandbox.create(timeout=60) # Change the sandbox timeout to 30 seconds. # 🚨 The new timeout will be 30 seconds from now. sandbox.set_timeout(30) ``` ## Retrieve sandbox information You can retrieve sandbox information like sandbox ID, template, metadata, started at/end at date by calling the `getInfo` method in JavaScript or `get_info` method in Python. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with and keep it running for 60 seconds. const sandbox = await Sandbox.create({ timeoutMs: 60_000 }) // Retrieve sandbox information. const info = await sandbox.getInfo() console.log(info) // { // "sandboxId": "iiny0783cype8gmoawzmx-ce30bc46", // "templateId": "rki5dems9wqfm4r03t7g", // "name": "base", // "metadata": {}, // "startedAt": "2025-03-24T15:37:58.076Z", // "endAt": "2025-03-24T15:42:58.076Z" // } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create sandbox with and keep it running for 60 seconds. sandbox = Sandbox.create(timeout=60) # Retrieve sandbox information. info = sandbox.get_info() print(info) # SandboxInfo(sandbox_id='ig6f1yt6idvxkxl562scj-419ff533', # template_id='u7nqkmpn3jjf1tvftlsu', # name='base', # metadata={}, # started_at=datetime.datetime(2025, 3, 24, 15, 42, 59, 255612, tzinfo=tzutc()), # end_at=datetime.datetime(2025, 3, 24, 15, 47, 59, 255612, tzinfo=tzutc()) # ) ``` ## Shutdown sandbox You can shutdown the sandbox any time even before the timeout is up by calling the `kill` method. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with and keep it running for 60 seconds. const sandbox = await Sandbox.create({ timeoutMs: 60_000 }) // Shutdown the sandbox immediately. await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create sandbox with and keep it running for 60 seconds. sandbox = Sandbox.create(timeout=60) # Shutdown the sandbox immediately. sandbox.kill() ``` # Auto-resume on request Source: https://e2b.mintlify.app/docs/sandbox/auto-resume Manage sandbox lifecycle and auto-resume on activity. Many workloads don't need a sandbox running all the time, but when they do, it should just work — whether the sandbox was paused or not. Auto-resume handles this automatically: a paused sandbox wakes up when activity arrives, so your code doesn't have to check or manage sandbox state. Auto-resume builds on the sandbox [persistence](/docs/sandbox/persistence) lifecycle. ## Configure Set the `lifecycle` object when creating a sandbox to control what happens on timeout and whether paused sandboxes should auto-resume. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ timeoutMs: 10 * 60 * 1000, lifecycle: { onTimeout: 'pause', autoResume: true, // resume when activity arrives }, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create( timeout=10 * 60, lifecycle={ "on_timeout": "pause", "auto_resume": True, # resume when activity arrives }, ) ``` ### Lifecycle options The `lifecycle` object accepts the following options: | Setting | Option | Description | | -------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `onTimeout` (JavaScript) / `on_timeout` (Python) | `"kill"` | (default) Sandbox is terminated when timeout is reached | | | `"pause"` | Sandbox is paused (full memory + filesystem snapshot) when timeout is reached | | | `{ action: "pause", keepMemory: false }` | Filesystem-only auto-pause — drops memory so resume cold-boots (Python: `{"action": "pause", "keep_memory": False}`). Cannot be combined with auto-resume. See [Filesystem-only snapshots](/docs/sandbox/filesystem-only-snapshots) | | `autoResume` (JavaScript) / `auto_resume` (Python) | `false` | (default) Paused sandboxes do not auto-resume | | | `true` | Paused sandboxes auto-resume on activity. Valid only when `onTimeout` / `on_timeout` is `"pause"` and the snapshot keeps memory (not filesystem-only) | If `lifecycle.autoResume` / `lifecycle.auto_resume` is falsy or unset, you can still resume a paused sandbox manually with [`Sandbox.connect()`](/docs/sandbox/connect). `onTimeout` / `on_timeout` also accepts an object form that controls the snapshot kind via `keepMemory` / `keep_memory`. Setting it to `false` makes the timeout auto-pause [filesystem-only](/docs/sandbox/filesystem-only-snapshots) — resume cold-boots the sandbox instead of restoring it in place. Because such a snapshot can't be woken by traffic, it can't be combined with auto-resume. ## Timeout after auto-resume When a sandbox auto-resumes, it restarts with a **5-minute minimum timeout**. If you originally created the sandbox with a longer timeout, that value carries over. The countdown begins from the moment the sandbox resumes, not from when it was first created. For example, if you create a sandbox with a 2-minute timeout: 1. The sandbox runs for 2 minutes, then pauses. 2. Activity arrives and the sandbox auto-resumes. 3. A 5-minute timeout starts (the 5-minute minimum applies because the original timeout was shorter). 4. If no further activity resets the timeout, the sandbox pauses again after 5 minutes. If you create a sandbox with a 1-hour timeout, the auto-resume timeout will also be 1 hour, since it exceeds the 5-minute minimum. This cycle repeats every time the sandbox auto-resumes — the lifecycle configuration is persistent across pause/resume cycles. You can change the timeout after the sandbox resumes by calling `setTimeout()` (JavaScript) / `set_timeout()` (Python). See [Change sandbox timeout during runtime](/docs/sandbox#change-sandbox-timeout-during-runtime). ## What counts as activity Auto-resume is triggered by sandbox activity — both HTTP traffic and SDK operations. That includes: * `sandbox.commands.run(...)` * `sandbox.files.read(...)` * `sandbox.files.write(...)` * Opening a tunneled app URL or sending requests to a service running inside the sandbox If a sandbox is paused and `lifecycle.autoResume` (JavaScript) / `lifecycle.auto_resume` (Python) is enabled, the next supported operation resumes it automatically. You do not need to call [`Sandbox.connect()`](/docs/sandbox/connect) first. ### SDK example: pause, then read a file The following example writes a file, pauses the sandbox, then reads the file back. The read operation triggers an automatic resume. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ timeoutMs: 10 * 60 * 1000, lifecycle: { onTimeout: 'pause', autoResume: true, }, }) await sandbox.files.write('/home/user/hello.txt', 'hello from a paused sandbox') await sandbox.pause() const content = await sandbox.files.read('/home/user/hello.txt') console.log(content) console.log(`State after read: ${(await sandbox.getInfo()).state}`) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create( timeout=10 * 60, lifecycle={ "on_timeout": "pause", "auto_resume": True, }, ) sandbox.files.write("/home/user/hello.txt", "hello from a paused sandbox") sandbox.pause() content = sandbox.files.read("/home/user/hello.txt") print(content) print(f"State after read: {sandbox.get_info().state}") ``` ## Example: Web server with auto-resume Auto-resume is especially useful for web servers and preview environments. When an HTTP request arrives at a paused sandbox, the sandbox wakes up automatically to handle it. The following example starts a simple HTTP server and retrieves its public URL. Use [`getHost()`](/docs/network/public-url#sandbox-public-url) (JavaScript) / [`get_host()`](/docs/network/public-url#sandbox-public-url) (Python) to get the sandbox's publicly accessible hostname for a given port. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ timeoutMs: 5 * 60 * 1000, lifecycle: { onTimeout: 'pause', autoResume: true, }, }) await sandbox.commands.run('python3 -m http.server 3000', { background: true }) const host = sandbox.getHost(3000) // Once the sandbox times out and pauses, any request to the preview URL will automatically resume it. console.log(`Preview URL: https://${host}`) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create( timeout=5 * 60, lifecycle={ "on_timeout": "pause", "auto_resume": True, }, ) sandbox.commands.run("python3 -m http.server 3000", background=True) host = sandbox.get_host(3000) # Once the sandbox times out and pauses, any request to the preview URL will automatically resume it. print(f"Preview URL: https://{host}") ``` ## Cleanup Auto-resume is persistent — if your sandbox resumes and later times out again, it will pause again. Each time the sandbox resumes, it gets a fresh timeout (at least 5 minutes, or longer if the original creation timeout exceeds that) — so the sandbox keeps cycling between running and paused as long as activity arrives. If you call `.kill()`, the sandbox is permanently deleted and cannot be resumed. # Connect to running sandbox Source: https://e2b.mintlify.app/docs/sandbox/connect If you have a running sandbox, you can connect to it using the `Sandbox.connect()` method and then start controlling it with our SDK. This is useful if you want to, for example, reuse the same sandbox instance for the same user after a short period of inactivity. ## 1. Get the sandbox ID To connect to a running sandbox, you first need to retrieve its ID. You can do this by calling the `Sandbox.list()` method. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() // Get all running sandboxes const paginator = await Sandbox.list({ query: { state: ['running'] }, }) const runningSandboxes = await paginator.nextItems() if (runningSandboxes.length === 0) { throw new Error('No running sandboxes found') } // Get the ID of the sandbox you want to connect to const sandboxId = runningSandboxes[0].sandboxId ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() # Get all running sandboxes paginator = Sandbox.list() # Get the ID of the sandbox you want to connect to running_sandboxes = paginator.next_items() if len(running_sandboxes) == 0: raise Exception("No running sandboxes found") # Get the ID of the sandbox you want to connect to sandbox_id = running_sandboxes[0].sandbox_id ``` ## 2. Connect to the sandbox Now that you have the sandbox ID, you can connect to the sandbox using the `Sandbox.connect()` method. ```js JavaScript & TypeScript highlight={3} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.connect(sandboxId) // Now you can use the sandbox as usual // ... const result = await sandbox.commands.run("whoami") console.log(`Running in sandbox ${sandbox.sandboxId} as "${result.stdout.trim()}"`) ``` ```python Python highlight={3} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.connect(sandbox_id) # Now you can use the sandbox as usual # ... r = sandbox.commands.run("whoami") print(f"Running in sandbox {sandbox.sandbox_id} as \"{r.stdout.strip()}\"") ``` # Environment variables Source: https://e2b.mintlify.app/docs/sandbox/environment-variables This page covers how to set and use environment variables in a sandbox, and default environment variables inside the sandbox. ## Default environment variables ### Knowing if you are inside a sandbox Sometimes it's useful to know if the code is running inside a sandbox. Upon creating a sandbox, useful sandbox metadata is set as environment variables for commands: * `E2B_SANDBOX` is set to `true` for processes to know if they are inside our VM. * `E2B_SANDBOX_ID` to know the ID of the sandbox. * `E2B_TEAM_ID` to know the team ID that created the sandbox. * `E2B_TEMPLATE_ID` to know what template was used for the current sandbox. You can try it out by running the following code in the sandbox: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const sandbox = await Sandbox.create() const result = await sandbox.commands.run('echo $E2B_SANDBOX_ID') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sandbox = Sandbox.create() result = sandbox.commands.run("echo $E2B_SANDBOX_ID") ``` These default environment variables are only accessible via the SDK, when using the CLI you can find them in the form of dot files in the `/run/e2b/` dir: ```sh theme={"theme":{"light":"github-light","dark":"github-dark-default"}} user@e2b:~$ ls -a /run/e2b/ .E2B_SANDBOX .E2B_SANDBOX_ID .E2B_TEAM_ID .E2B_TEMPLATE_ID ``` *** ## Setting environment variables There are 3 ways to set environment variables in a sandbox: 1. [Global environment variables when creating the sandbox](/docs/sandbox/environment-variables#1-global-environment-variables). 2. [When running code in the sandbox](/docs/sandbox/environment-variables#2-setting-environment-variables-when-running-code). 3. [When running commands in the sandbox](/docs/sandbox/environment-variables#3-setting-environment-variables-when-running-commands). ### 1. Global environment variables You can set global environment variables when creating a sandbox. ```js JavaScript & TypeScript highlight={2-4} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const sandbox = await Sandbox.create({ envs: { MY_VAR: 'my_value', }, }) ``` ```python Python highlight={2-4} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sandbox = Sandbox.create( envs={ 'MY_VAR': 'my_value', }, ) ``` ### 2. Setting environment variables when running code You can set environment variables for specific code execution call in the sandbox. * These environment variables are scoped to the command but are not private in the OS. * If you had a global environment variable with the same name, it will be overridden only for the command. ```js JavaScript & TypeScript highlight={3-5} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const sandbox = await Sandbox.create() const result = await sandbox.runCode('import os; print(os.environ.get("MY_VAR"))', { envs: { MY_VAR: 'my_value', }, }) ``` ```python Python highlight={4-6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sandbox = Sandbox.create() result = sandbox.run_code( 'import os; print(os.environ.get("MY_VAR"))', envs={ 'MY_VAR': 'my_value' } ) ``` ### 3. Setting environment variables when running commands You can set environment variables for specific command execution in the sandbox. * These environment variables are scoped to the command but are not private in the OS. * If you had a global environment variable with the same name, it will be overridden only for the command. ```js JavaScript & TypeScript highlight={3-5} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const sandbox = await Sandbox.create() sandbox.commands.run('echo $MY_VAR', { envs: { MY_VAR: '123', }, }) ``` ```python Python highlight={4-6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sandbox = Sandbox.create() sandbox.commands.run( 'echo $MY_VAR', envs={ 'MY_VAR': '123' } ) ``` # Filesystem-only snapshots Source: https://e2b.mintlify.app/docs/sandbox/filesystem-only-snapshots Persist only the sandbox filesystem for a lighter snapshot that reboots on resume. When you [pause](/docs/sandbox/persistence) a sandbox, E2B saves a snapshot you can resume later. By default that snapshot includes **both the filesystem and the memory**, so resuming restores the sandbox exactly as it was — running processes and in-memory state included. A **filesystem-only snapshot** persists only the filesystem. There is no memory snapshot, so resuming **reboots** the sandbox from its disk: the files on disk survive, but RAM and running processes do not. ## Full vs. filesystem-only | | Full snapshot (default) | Filesystem-only snapshot | | ----------------------------------- | ----------------------- | ------------------------ | | Filesystem (disk) | Preserved | Preserved | | Memory (RAM) | Preserved | **Disposed** | | Running processes | Restored | **Terminated** (reboot) | | In-memory state (variables, caches) | Restored | **Discarded** (reboot) | | Resume behavior | Restored in place | **Reboot** from disk | | Speed | Baseline | **Faster** | Network connections to clients **outside** the sandbox drop when it pauses — in both cases — and must be re-established on resume (see [Persistence](/docs/sandbox/persistence#network)). The in-sandbox difference: a full snapshot keeps the process running so it can serve again immediately, while a filesystem-only snapshot reboots, so the service must be restarted first. Use a filesystem-only snapshot when you only care about the data on disk and don't need running processes restored — for example, persisting a workspace's files between sessions, or checkpointing build/scratch state. The snapshot is lighter to store because there's no memory image, at the cost of a reboot on resume. ## Pause filesystem-only Pass `keepMemory: false` (JavaScript) / `keep_memory=False` (Python) to `pause()`. The filesystem is saved; memory is dropped. ```js JavaScript & TypeScript highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() // Save only the filesystem — resuming will reboot the sandbox await sbx.pause({ keepMemory: false }) // Resume: the sandbox reboots from disk (files survive, processes do not) const sameSbx = await sbx.connect() ``` ```python Python highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() # Save only the filesystem — resuming will reboot the sandbox sbx.pause(keep_memory=False) # Resume: the sandbox reboots from disk (files survive, processes do not) same_sbx = sbx.connect() ``` `keepMemory` / `keep_memory` defaults to `true`, so omitting it (or calling `pause()`) takes a full memory snapshot — see [Persistence](/docs/sandbox/persistence). `keepMemory` is a per-pause choice, not a sandbox-wide mode. Pausing once with `keepMemory: false` / `keep_memory=False` does not lock the sandbox into filesystem-only: after you resume, the next `pause()` takes a full memory snapshot again unless you pass `keepMemory: false` / `keep_memory=False` once more. ## Auto-pause filesystem-only You can also make the timeout-driven [auto-pause](/docs/sandbox/persistence#auto-pause) filesystem-only. Set `onTimeout` / `on_timeout` to the object form and include `keepMemory` / `keep_memory`: ```js JavaScript & TypeScript highlight={5} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ lifecycle: { onTimeout: { action: 'pause', keepMemory: false }, }, }) ``` ```python Python highlight={5} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create( lifecycle={ "on_timeout": {"action": "pause", "keep_memory": False}, }, ) ``` When this sandbox times out, it auto-pauses filesystem-only; the next explicit resume reboots it. See [Auto-resume on request](/docs/sandbox/auto-resume) for the full `lifecycle` reference. ## Type safety and validation `keepMemory` / `keep_memory` only governs a `pause` action, so the SDK rejects nonsensical combinations: * It cannot be set when the action is `kill`. In TypeScript this is a **compile-time type error** (the `onTimeout` object is a discriminated union on `action`); in Python it raises `InvalidArgumentException`. * It cannot be combined with **auto-resume**. A filesystem-only snapshot can't be woken by inbound traffic (auto-resume restores the running process from memory, which a filesystem-only snapshot doesn't have), so it must be resumed explicitly with [`connect()`](/docs/sandbox/connect). Setting `keepMemory: false` / `keep_memory=False` together with `autoResume: true` / `auto_resume=True` is rejected client-side. A filesystem-only snapshot is **not** compatible with auto-resume. Resume it explicitly via `connect()` — it will not wake on traffic. ## What changes on resume Resuming a filesystem-only snapshot is a reboot, so it takes roughly a fresh boot rather than the near-instant in-place memory restore. Any service running inside the sandbox must be restarted after resume, and clients must reconnect. Because resume is a reboot, anything that lived only in memory is gone, while everything written to disk persists. The example below writes a file and starts a background process, pauses filesystem-only, then resumes: the file is still there, but the process is not. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() await sbx.files.write('/home/user/data.txt', 'survives the reboot') const cmd = await sbx.commands.run('sleep 600', { background: true }) await sbx.pause({ keepMemory: false }) const resumed = await sbx.connect() // File on disk survives the reboot const content = await resumed.files.read('/home/user/data.txt') console.log(content) // "survives the reboot" // The background process is gone — the sandbox rebooted const check = await resumed.commands.run(`kill -0 ${cmd.pid}`, { background: false }) console.log(check.exitCode !== 0) // true — process no longer running ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() sbx.files.write("/home/user/data.txt", "survives the reboot") cmd = sbx.commands.run("sleep 600", background=True) sbx.pause(keep_memory=False) resumed = sbx.connect() # File on disk survives the reboot content = resumed.files.read("/home/user/data.txt") print(content) # "survives the reboot" # The background process is gone — the sandbox rebooted check = resumed.commands.run(f"kill -0 {cmd.pid}", background=False) print(check.exit_code != 0) # True — process no longer running ``` ## Related * [Sandbox persistence](/docs/sandbox/persistence) — full (memory + filesystem) pause and resume. * [Auto-resume on request](/docs/sandbox/auto-resume) — the full `lifecycle` configuration reference. * [Connect to a sandbox](/docs/sandbox/connect) — resume a paused sandbox explicitly. # Git integration Source: https://e2b.mintlify.app/docs/sandbox/git-integration Clone repositories, manage branches, and push changes using the sandbox.git methods. Use the `sandbox.git` methods to run common git operations inside a sandbox. ### Authentication and Identity #### Passing credentials inline For private repositories over HTTP(S), pass `username` and `password` (token) directly to commands that need authentication. A username is required whenever you pass a password/token. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' await sandbox.git.push(repoPath, { username: process.env.GIT_USERNAME, password: process.env.GIT_TOKEN, }) await sandbox.git.pull(repoPath, { username: process.env.GIT_USERNAME, password: process.env.GIT_TOKEN, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os repo_path = "/home/user/repo" sandbox.git.push( repo_path, username=os.environ.get("GIT_USERNAME"), password=os.environ.get("GIT_TOKEN"), ) sandbox.git.pull( repo_path, username=os.environ.get("GIT_USERNAME"), password=os.environ.get("GIT_TOKEN"), ) ``` #### Credential helper (authenticate once) To avoid passing credentials on each command, store them in the git credential helper inside the sandbox using `dangerouslyAuthenticate()` / `dangerously_authenticate()`. Stores credentials on disk inside the sandbox. Any process or agent with access to the sandbox can read them. Use only when you understand the risk. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Default (GitHub) await sandbox.git.dangerouslyAuthenticate({ username: process.env.GIT_USERNAME, password: process.env.GIT_TOKEN, }) // Custom host (self-hosted) await sandbox.git.dangerouslyAuthenticate({ username: process.env.GIT_USERNAME, password: process.env.GIT_TOKEN, host: 'git.example.com', protocol: 'https', }) // After this, HTTPS git operations use the stored credentials await sandbox.git.clone('https://git.example.com/org/repo.git', { path: '/home/user/repo' }) await sandbox.git.push('/home/user/repo') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os # Default (GitHub) sandbox.git.dangerously_authenticate( username=os.environ.get("GIT_USERNAME"), password=os.environ.get("GIT_TOKEN"), ) # Custom host (self-hosted) sandbox.git.dangerously_authenticate( username=os.environ.get("GIT_USERNAME"), password=os.environ.get("GIT_TOKEN"), host="git.example.com", protocol="https", ) # After this, HTTPS git operations use the stored credentials sandbox.git.clone("https://git.example.com/org/repo.git", path="/home/user/repo") sandbox.git.push("/home/user/repo") ``` #### Keep credentials in the remote URL By default, credentials are stripped from the remote URL after cloning. To keep credentials in the remote URL (stored in `.git/config`), set `dangerouslyStoreCredentials` / `dangerously_store_credentials`. Storing credentials in the remote URL persists them in the repo config. Any process or agent with access to the sandbox can read them. Only use this when required. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Default: credentials are stripped from the remote URL await sandbox.git.clone('https://git.example.com/org/repo.git', { path: '/home/user/repo', username: process.env.GIT_USERNAME, password: process.env.GIT_TOKEN, }) // Keep credentials in the remote URL await sandbox.git.clone('https://git.example.com/org/repo.git', { path: '/home/user/repo', username: process.env.GIT_USERNAME, password: process.env.GIT_TOKEN, dangerouslyStoreCredentials: true, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import os # Default: credentials are stripped from the remote URL sandbox.git.clone( "https://git.example.com/org/repo.git", path="/home/user/repo", username=os.environ.get("GIT_USERNAME"), password=os.environ.get("GIT_TOKEN"), ) # Keep credentials in the remote URL sandbox.git.clone( "https://git.example.com/org/repo.git", path="/home/user/repo", username=os.environ.get("GIT_USERNAME"), password=os.environ.get("GIT_TOKEN"), dangerously_store_credentials=True, ) ``` #### Configure git identity Set the git author name and email for commits. Configure globally or per-repository. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' // Global config await sandbox.git.configureUser('E2B Bot', 'bot@example.com') // Repo-local config await sandbox.git.configureUser('E2B Bot', 'bot@example.com', { scope: 'local', path: repoPath }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_path = "/home/user/repo" # Global config sandbox.git.configure_user("E2B Bot", "bot@example.com") # Repo-local config sandbox.git.configure_user( "E2B Bot", "bot@example.com", scope="local", path=repo_path ) ``` ### Clone a repository See [Authentication and Identity](#authentication-and-identity) for how to authenticate with private repositories. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoUrl = 'https://git.example.com/org/repo.git' const repoPath = '/home/user/repo' // Default clone await sandbox.git.clone(repoUrl, { path: repoPath }) // Clone a specific branch await sandbox.git.clone(repoUrl, { path: repoPath, branch: 'main' }) // Shallow clone await sandbox.git.clone(repoUrl, { path: repoPath, depth: 1 }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_url = "https://git.example.com/org/repo.git" repo_path = "/home/user/repo" # Default clone sandbox.git.clone(repo_url, path=repo_path) # Clone a specific branch sandbox.git.clone(repo_url, path=repo_path, branch="main") # Shallow clone sandbox.git.clone(repo_url, path=repo_path, depth=1) ``` ### Check status and branches `status()` returns a structured object with branch, ahead/behind, and file status details. `branches()` returns the branch list and the current branch. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' const status = await sandbox.git.status(repoPath) console.log(status.currentBranch, status.ahead, status.behind) console.log(status.fileStatus) const branches = await sandbox.git.branches(repoPath) console.log(branches.currentBranch) console.log(branches.branches) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_path = "/home/user/repo" status = sandbox.git.status(repo_path) print(status.current_branch, status.ahead, status.behind) print(status.file_status) branches = sandbox.git.branches(repo_path) print(branches.current_branch) print(branches.branches) ``` ### Create and manage branches ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' // Create and switch to a new branch await sandbox.git.createBranch(repoPath, 'feature/new-docs') // Check out an existing branch await sandbox.git.checkoutBranch(repoPath, 'main') // Delete a branch await sandbox.git.deleteBranch(repoPath, 'feature/old-docs') // Force delete a branch await sandbox.git.deleteBranch(repoPath, 'feature/stale-docs', { force: true }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_path = "/home/user/repo" # Create and switch to a new branch sandbox.git.create_branch(repo_path, "feature/new-docs") # Check out an existing branch sandbox.git.checkout_branch(repo_path, "main") # Delete a branch sandbox.git.delete_branch(repo_path, "feature/old-docs") # Force delete a branch sandbox.git.delete_branch(repo_path, "feature/stale-docs", force=True) ``` ### Stage and commit ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' // Default: stage all changes, commit with repo config await sandbox.git.add(repoPath) await sandbox.git.commit(repoPath, 'Initial commit') // Stage specific files await sandbox.git.add(repoPath, { files: ['README.md', 'src/index.ts'] }) // Allow empty commit and override author await sandbox.git.commit(repoPath, 'Docs sync', { authorName: 'E2B Bot', authorEmail: 'bot@example.com', allowEmpty: true, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_path = "/home/user/repo" # Default: stage all changes, commit with repo config sandbox.git.add(repo_path) sandbox.git.commit(repo_path, "Initial commit") # Stage specific files sandbox.git.add(repo_path, files=["README.md", "src/index.ts"]) # Allow empty commit and override author sandbox.git.commit( repo_path, "Docs sync", author_name="E2B Bot", author_email="bot@example.com", allow_empty=True, ) ``` ### Pull and push See [Authentication and Identity](#authentication-and-identity) for how to authenticate with private repositories. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' // Default (uses upstream when set) await sandbox.git.push(repoPath) await sandbox.git.pull(repoPath) // Target a specific remote/branch and set upstream await sandbox.git.push(repoPath, { remote: 'origin', branch: 'main', setUpstream: true, }) await sandbox.git.pull(repoPath, { remote: 'origin', branch: 'main', }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_path = "/home/user/repo" # Default (uses upstream when set) sandbox.git.push(repo_path) sandbox.git.pull(repo_path) # Target a specific remote/branch and set upstream sandbox.git.push( repo_path, remote="origin", branch="main", set_upstream=True, ) sandbox.git.pull( repo_path, remote="origin", branch="main", ) ``` ### Manage remotes ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' const repoUrl = 'https://git.example.com/org/repo.git' // Default await sandbox.git.remoteAdd(repoPath, 'origin', repoUrl) // Fetch after adding the remote await sandbox.git.remoteAdd(repoPath, 'origin', repoUrl, { fetch: true }) // Overwrite the remote URL if it already exists await sandbox.git.remoteAdd(repoPath, 'origin', repoUrl, { overwrite: true }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_path = "/home/user/repo" repo_url = "https://git.example.com/org/repo.git" # Default sandbox.git.remote_add(repo_path, "origin", repo_url) # Fetch after adding the remote sandbox.git.remote_add(repo_path, "origin", repo_url, fetch=True) # Overwrite the remote URL if it already exists sandbox.git.remote_add(repo_path, "origin", repo_url, overwrite=True) ``` ### Git config Set and get git configuration values. See [Configure git identity](#configure-git-identity) for configuring the commit author. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const repoPath = '/home/user/repo' // Global config await sandbox.git.setConfig('pull.rebase', 'false') const rebase = await sandbox.git.getConfig('pull.rebase') // Repo-local config await sandbox.git.setConfig('pull.rebase', 'false', { scope: 'local', path: repoPath }) const localRebase = await sandbox.git.getConfig('pull.rebase', { scope: 'local', path: repoPath }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} repo_path = "/home/user/repo" # Global config sandbox.git.set_config("pull.rebase", "false") rebase = sandbox.git.get_config("pull.rebase") # Repo-local config sandbox.git.set_config("pull.rebase", "false", scope="local", path=repo_path) local_rebase = sandbox.git.get_config("pull.rebase", scope="local", path=repo_path) ``` # Monitor sandbox lifecycle events Source: https://e2b.mintlify.app/docs/sandbox/lifecycle-events-api The lifecycle API provides RESTful endpoints to request the latest sandbox lifecycle events. This allows you to track when sandboxes are created, paused, resumed, updated, snapshotted, or killed, along with metadata. All requests require authentication using your team [API key](/docs/api-key#where-to-find-api-key). ## Retention Sandbox lifecycle events are retained for **7 days** by default. After the retention window passes, a sandbox's events are no longer available through this API. Query Parameters: * `offset` (optional): Number of events to skip (default: 0, min: 0) * `limit` (optional): Number of events to return (default: 10, min: 1, max: 100) * `orderAsc` (optional): Sort order - true for ascending, false for descending (default: false) * `types` (optional): Filter by event type. Can be specified multiple times to match any of the given types. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() // Get the latest events for a specific sandbox const resp1 = await fetch( `https://api.e2b.app/events/sandboxes/${sbx.id}`, { method: 'GET', headers: { 'X-API-Key': E2B_API_KEY, }, } ) const sandboxEvents = await resp1.json() // Get the latest 10 events for all sandboxes associated with the team const resp2 = await fetch( 'https://api.e2b.app/events/sandboxes?limit=10', { method: 'GET', headers: { 'X-API-Key': E2B_API_KEY, }, } ) const teamSandboxEvents = await resp2.json() // Filter events by type const resp3 = await fetch( 'https://api.e2b.app/events/sandboxes?types=sandbox.lifecycle.created&types=sandbox.lifecycle.killed', { method: 'GET', headers: { 'X-API-Key': E2B_API_KEY, }, } ) const filteredEvents = await resp3.json() console.log(teamSandboxEvents) // [ // { // "version": "v1", // "id": "f5911677-cb60-498f-afed-f68143b3cc59", // "type": "sandbox.lifecycle.killed", // "eventData": null, // "sandboxBuildId": "a979a14b-bdcc-49e6-bc04-1189fc9fe7c2", // "sandboxExecutionId": "1dae9e1c-9957-4ce7-a236-a99d5779aadf", // "sandboxId": "${SANDBOX_ID}", // "sandboxTeamId": "460355b3-4f64-48f9-9a16-4442817f79f5", // "sandboxTemplateId": "rki5dems9wqfm4r03t7g", // "timestamp": "2025-08-06T20:59:36Z" // }, // { // "version": "v1", // "id": "30b09e11-9ba2-42db-9cf6-d21f0f43a234", // "type": "sandbox.lifecycle.updated", // "eventData": { // "set_timeout": "2025-08-06T20:59:59Z" // }, // "sandboxBuildId": "a979a14b-bdcc-49e6-bc04-1189fc9fe7c2", // "sandboxExecutionId": "1dae9e1c-9957-4ce7-a236-a99d5779aadf", // "sandboxId": "${SANDBOX_ID}", // "sandboxTeamId": "460355b3-4f64-48f9-9a16-4442817f79f5", // "sandboxTemplateId": "rki5dems9wqfm4r03t7g", // "timestamp": "2025-08-06T20:59:29Z" // }, // [...] // { // "version": "v1", // "id": "0568572b-a2ac-4e5f-85fa-fae90905f556", // "type": "sandbox.lifecycle.created", // "eventData": null, // "sandboxBuildId": "a979a14b-bdcc-49e6-bc04-1189fc9fe7c2", // "sandboxExecutionId": "1dae9e1c-9957-4ce7-a236-a99d5779aadf", // "sandboxId": "${SANDBOX_ID}", // "sandboxTeamId": "460355b3-4f64-48f9-9a16-4442817f79f5", // "sandboxTemplateId": "rki5dems9wqfm4r03t7g", // "timestamp": "2025-08-06T20:59:24Z" // } // ] ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests from e2b import Sandbox sbx = Sandbox.create() # Get the latest events for a specific sandbox resp1 = requests.get( f"https://api.e2b.app/events/sandboxes/{sbx.sandbox_id}", headers={ "X-API-Key": E2B_API_KEY, } ) sandbox_events = resp1.json() # Get the latest 10 events for all sandboxes associated with the team resp2 = requests.get( "https://api.e2b.app/events/sandboxes?limit=10", headers={ "X-API-Key": E2B_API_KEY, } ) team_sandbox_events = resp2.json() # Filter events by type resp3 = requests.get( "https://api.e2b.app/events/sandboxes", params={"types": ["sandbox.lifecycle.created", "sandbox.lifecycle.killed"]}, headers={ "X-API-Key": E2B_API_KEY, } ) filtered_events = resp3.json() print(team_sandbox_events) # [ # { # "version": "v1", # "id": "0568572b-a2ac-4e5f-85fa-fae90905f556", # "type": "sandbox.lifecycle.killed", # "eventData": null, # "sandboxBuildId": "a979a14b-bdcc-49e6-bc04-1189fc9fe7c2", # "sandboxExecutionId": "1dae9e1c-9957-4ce7-a236-a99d5779aadf", # "sandboxId": "${SANDBOX_ID}", # "sandboxTeamId": "460355b3-4f64-48f9-9a16-4442817f79f5", # "sandboxTemplateId": "rki5dems9wqfm4r03t7g", # "timestamp": "2025-08-06T20:59:36Z" # }, # { # "version": "v1", # "id": "e7013704-2c51-4dd2-9f6c-388c91460149", # "type": "sandbox.lifecycle.updated", # "eventData": { # "set_timeout": "2025-08-06T20:59:59Z" # }, # "sandboxBuildId": "a979a14b-bdcc-49e6-bc04-1189fc9fe7c2", # "sandboxExecutionId": "1dae9e1c-9957-4ce7-a236-a99d5779aadf", # "sandboxId": "${SANDBOX_ID}", # "sandboxTeamId": "460355b3-4f64-48f9-9a16-4442817f79f5", # "sandboxTemplateId": "rki5dems9wqfm4r03t7g", # "timestamp": "2025-08-06T20:59:29Z" # }, # [...] # { # "version": "v1", # "id": "f29ef778-2743-4c97-a802-7ba67f84ce24", # "type": "sandbox.lifecycle.created", # "eventData": null, # "sandboxBuildId": "a979a14b-bdcc-49e6-bc04-1189fc9fe7c2", # "sandboxExecutionId": "1dae9e1c-9957-4ce7-a236-a99d5779aadf", # "sandboxId": "${SANDBOX_ID}", # "sandboxTeamId": "460355b3-4f64-48f9-9a16-4442817f79f5", # "sandboxTemplateId": "rki5dems9wqfm4r03t7g", # "timestamp": "2025-08-06T20:59:24Z" # } # ] ``` # Sandbox lifecycle webhooks Source: https://e2b.mintlify.app/docs/sandbox/lifecycle-events-webhooks Webhooks provide a way for notifications to be delivered to an external web server whenever certain sandbox lifecycle events occur. This allows you to receive real-time updates about sandbox creation, updates, and termination without having to poll the API. All webhook requests require authentication using your team [API key](/docs/api-key#where-to-find-api-key). ## Webhook management ### Register webhook Register a new webhook to receive sandbox lifecycle events. The webhook will be registered for the team ID associated with your API key. All events specified during webhook creation will be sent to URL provided during registration with [following payload](#webhook-payload). ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Register a new webhook const resp = await fetch( 'https://api.e2b.app/events/webhooks', { method: 'POST', headers: { 'X-API-Key': E2B_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'My Sandbox Webhook', url: 'https://your-webhook-endpoint.com/webhook', enabled: true, events: ['sandbox.lifecycle.created', 'sandbox.lifecycle.updated', 'sandbox.lifecycle.killed'], signatureSecret: 'secret-for-event-signature-verification' }), } ) if (resp.status === 201) { console.log('Webhook registered successfully') } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests # Register a new webhook resp = requests.post( "https://api.e2b.app/events/webhooks", headers={ "X-API-Key": E2B_API_KEY, "Content-Type": "application/json", }, json={ "name": "My Sandbox Webhook", "url": "https://your-webhook-endpoint.com/webhook", "enabled": True, "events": ["sandbox.lifecycle.created", "sandbox.lifecycle.updated", "sandbox.lifecycle.killed"], "signatureSecret": "secret-for-event-signature-verification" } ) if resp.status_code == 201: print("Webhook registered successfully") ``` ### List webhooks List all registered webhooks for your team. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // List webhooks const resp = await fetch( 'https://api.e2b.app/events/webhooks', { method: 'GET', headers: { 'X-API-Key': E2B_API_KEY }, }, ) if (resp.status === 200) { console.log('Webhooks listed successfully') console.log(await resp.json()) } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests # List webhooks resp = requests.get( "https://api.e2b.app/events/webhooks", headers={ "X-API-Key": E2B_API_KEY }, ) if resp.status_code == 200: print("Webhooks listed successfully") print(resp.json()) ``` ### Get webhook configuration Retrieve the current webhook configuration for your team. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Get webhook configuration const resp = await fetch( `https://api.e2b.app/events/webhooks/${webhookID}`, { method: 'GET', headers: { 'X-API-Key': E2B_API_KEY, }, } ) const webhookConfig = await resp.json() console.log(webhookConfig) // { // "id": "", // "teamId": "", // "name": "My Sandbox Webhook", // "createdAt": "2025-08-06T21:00:00Z", // "enabled": true, // "url": "https://your-webhook-endpoint.com/webhook", // "events": ["sandbox.lifecycle.created", "sandbox.lifecycle.killed"] // } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests # Get webhook configuration resp = requests.get( "https://api.e2b.app/events/webhooks/{webhookID}", headers={ "X-API-Key": E2B_API_KEY, } ) webhook_config = resp.json() print(webhook_config) # { # "id": "", # "teamId": "", # "name": "My Sandbox Webhook", # "createdAt": "2025-08-06T21:00:00Z", # "enabled": true, # "url": "https://your-webhook-endpoint.com/webhook", # "events": ["sandbox.lifecycle.created", "sandbox.lifecycle.killed"] # } ``` ### Update webhook configuration Update an existing webhook configuration. The update will replace the previous configuration fields with provided fields. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Update webhook configuration const resp = await fetch( `https://api.e2b.app/events/webhooks/${webhookID}`, { method: 'PATCH', headers: { 'X-API-Key': E2B_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ url: 'https://your-updated-webhook-endpoint.com/webhook', enabled: false, events: ['sandbox.lifecycle.created'] }), } ) if (resp.status === 200) { console.log('Webhook updated successfully') } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests # Update webhook configuration resp = requests.patch( "https://api.e2b.app/events/webhooks/{webhookID}", headers={ "X-API-Key": E2B_API_KEY, "Content-Type": "application/json", }, json={ "url": "https://your-updated-webhook-endpoint.com/webhook", "enabled": False, "events": ["sandbox.lifecycle.created"] } ) if resp.status_code == 200: print("Webhook updated successfully") ``` ### Delete webhook Unregister the webhook. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Delete webhook configuration const resp = await fetch( `https://api.e2b.app/events/webhooks/${webhookID}`, { method: 'DELETE', headers: { 'X-API-Key': E2B_API_KEY, }, } ) if (resp.status === 200) { console.log('Webhook deleted successfully') } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import requests # Delete webhook configuration resp = requests.delete( "https://api.e2b.app/events/webhooks/{webhookID}", headers={ "X-API-Key": E2B_API_KEY, } ) if resp.status_code == 200: print("Webhook deleted successfully") ``` ## Webhook payload When a webhook is triggered, your endpoint will receive a POST request with a JSON payload containing the sandbox event data. The payload structure matches the event format from the API: ```json theme={"theme":{"light":"github-light","dark":"github-dark-default"}} { "id": "", "version": "v2", "type": "", "timestamp": "2025-08-06T20:59:24Z", "event_category": "lifecycle", "event_label": "kill", "event_data": { "sandbox_metadata": { "": "" }, "execution": { "started_at": "2025-08-06T20:58:24Z", "vcpu_count": 2, "memory_mb": 512, "execution_time": 1000 } }, "sandbox_id": "", "sandbox_execution_id": "", "sandbox_template_id": "", "sandbox_build_id": "", "sandbox_team_id": "" } ``` `event_data.execution` contains sandbox execution details and is included on `sandbox.lifecycle.killed` and `sandbox.lifecycle.paused` events: * `started_at` - UTC RFC3339 timestamp when the current sandbox execution started * `vcpu_count` - Number of vCPUs assigned to the sandbox * `memory_mb` - Memory assigned to the sandbox in MB * `execution_time` - Sandbox runtime in milliseconds # Webhook verification To ensure the authenticity of webhook requests, each request includes a signature in the `e2b-signature` header. You can verify the signature using the signature secret you provided when registering the webhook. This confirms that the request originated from E2B and has not been tampered with. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} function verifyWebhookSignature(secret : string, payload : string, payloadSignature : string) { const expectedSignatureRaw = crypto.createHash('sha256').update(secret + payload).digest('base64'); const expectedSignature = expectedSignatureRaw.replace(/=+$/, ''); return expectedSignature == payloadSignature } const payloadValid = verifyWebhookSignature(secret, webhookBodyRaw, webhookSignatureHeader) if (payloadValid) { console.log("Payload signature is valid") } else { console.log("Payload signature is INVALID") } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import hashlib import base64 def verify_webhook_signature(secret: str, payload: str, payload_signature: str) -> bool: hash_bytes = hashlib.sha256((secret + payload).encode('utf-8')).digest() expected_signature = base64.b64encode(hash_bytes).decode('utf-8') expected_signature = expected_signature.rstrip('=') return expected_signature == payload_signature if verify_webhook_signature(secret, webhook_body_raw, webhook_signature_header): print("Payload signature is valid") else: print("Payload signature is INVALID") ``` ```go Golang theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import ( "crypto/sha256" "encoding/base64" "fmt" "strings" ) func verifyWebhookSignature(secret, payload, payloadSignature string) bool { hash := sha256.Sum256([]byte(secret + payload)) expectedSignature := base64.StdEncoding.EncodeToString(hash[:]) expectedSignature = strings.TrimRight(expectedSignature, "=") return expectedSignature == payloadSignature } if verifyWebhookSignature(secret, webhookBodyString, webhookSignatureHeaderString) { fmt.Println("Payload signature is valid") } else { fmt.Println("Payload signature is INVALID") } ``` ## Webhook request headers When webhooks is send, we are adding headers to help you verify the authenticity of the request and make debugging easier. * `e2b-webhook-id` - Webhook ID that triggered the event * `e2b-delivery-id` - Unique ID for the delivery attempt * `e2b-signature-version` - Currently always `v1`, reserved for future use * `e2b-signature` - Signature for verifying the request authenticity\` ## Delivery and retries If a delivery fails, E2B retries it **up to 3 times, 10 seconds apart**. A delivery is treated as failed when your endpoint returns a 4xx or 5xx status code, the request times out, or it fails with a network error. Because a delivery can be retried, the same event may be delivered more than once. Make your webhook handler idempotent and use the `e2b-delivery-id` header to deduplicate deliveries you've already processed. ## Available event types The following event types can be subscribed to via webhooks, they are used as the `type` field in the [payload](#webhook-payload). * `sandbox.lifecycle.created` - Sandbox creation * `sandbox.lifecycle.killed` - Sandbox termination * `sandbox.lifecycle.updated` - Sandbox configuration updates * `sandbox.lifecycle.paused` - Sandbox pausing * `sandbox.lifecycle.resumed` - Sandbox resuming * `sandbox.lifecycle.checkpointed` - Sandbox [snapshot](/docs/sandbox/snapshots) created # List sandboxes Source: https://e2b.mintlify.app/docs/sandbox/list You can list sandboxes using the `Sandbox.list()` method. Once you have information about running sandbox, you can [connect](/docs/sandbox/connect) to it using the `Sandbox.connect()` method. ### Listing sandboxes The `Sandbox.list()` method supports pagination. In the [advanced pagination](/docs/sandbox/list#advanced-pagination) section, you can find more information about pagination techniques using the updated method. ```js JavaScript & TypeScript highlight={6,11,14,24} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox, SandboxInfo } from 'e2b' const sandbox = await Sandbox.create( { metadata: { name: 'My Sandbox', }, }, ) const paginator = Sandbox.list() // Get the first page of sandboxes (running and paused) const firstPage = await paginator.nextItems() const runningSandbox = firstPage[0] console.log('Running sandbox metadata:', runningSandbox.metadata) console.log('Running sandbox id:', runningSandbox.sandboxId) console.log('Running sandbox started at:', runningSandbox.startedAt) console.log('Running sandbox template id:', runningSandbox.templateId) // Get the next page of sandboxes const nextPage = await paginator.nextItems() ``` ```python Python highlight={5,9,12,22} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, SandboxInfo sandbox = Sandbox.create( metadata={ "name": "My Sandbox", }, ) paginator = Sandbox.list() # Get the first page of sandboxes (running and paused) firstPage = paginator.next_items() running_sandbox = firstPage[0] print('Running sandbox metadata:', running_sandbox.metadata) print('Running sandbox id:', running_sandbox.sandbox_id) print('Running sandbox started at:', running_sandbox.started_at) print('Running sandbox template id:', running_sandbox.template_id) # Get the next page of sandboxes nextPage = paginator.next_items() ``` The code above will output something like this: ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Running sandbox metadata: {name: "My Sandbox"} Running sandbox id: ixjj3iankaishgcge4jwn-b0b684e9 Running sandbox started at: 2024-10-15T21:13:07.311Z Running sandbox template id: 3e4rngfa34txe0gxc1zf ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Running sandbox metadata: {'name': 'My Sandbox'} Running sandbox id: ixjj3iankaishgcge4jwn-b0b684e9 Running sandbox started at: 2024-10-15 21:13:07.311861+00:00 Running sandbox template id: 3e4rngfa34txe0gxc1zf ``` ### Filtering sandboxes Filter sandboxes by their current state. The state parameter can contain either "**running**" for running sandboxes or "**paused**" for paused sandboxes, or both. ```js JavaScript & TypeScript highlight={9,13} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create a sandbox. const sandbox = await Sandbox.create() // List sandboxes that are running or paused. const paginator = Sandbox.list({ query: { state: ['running', 'paused'], }, }) const sandboxes = await paginator.nextItems() ``` ```python Python highlight={9,14} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, SandboxQuery, SandboxState # Create a sandbox with metadata. sandbox = Sandbox.create() # List sandboxes that are running or paused. paginator = Sandbox.list( query=SandboxQuery( state=[SandboxState.RUNNING, SandboxState.PAUSED], ), ) # Get the first page of sandboxes (running and paused) sandboxes = paginator.next_items() ``` Filter sandboxes by the metadata key value pairs specified during Sandbox creation. ```js JavaScript & TypeScript highlight={6-8,15,18} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with metadata. const sandbox = await Sandbox.create({ metadata: { env: 'dev', app: 'my-app', userId: '123', }, }) // List all sandboxes that has `userId` key with value `123` and `env` key with value `dev`. const paginator = Sandbox.list({ query: { metadata: { userId: '123', env: 'dev' }, }, }) const sandboxes = await paginator.nextItems() ``` ```python Python highlight={6-8,16-17} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, SandboxQuery, SandboxState # Create sandbox with metadata. sandbox = Sandbox.create( metadata={ "env": "dev", "app": "my-app", "user_id": "123", }, ) # List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. paginator = Sandbox.list( query=SandboxQuery( metadata={ "userId": "123", "env": "dev", } ), ) # Get the first page of sandboxes (running and paused) sandboxes = paginator.next_items() ``` ### Advanced pagination For more granular pagination, you can set custom per-page item limit (default and maximum is **100**) and specify an offset parameter (`nextToken` or `next_token`) to start paginating from. ```js JavaScript & TypeScript highlight={4-5,16} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const paginator = Sandbox.list({ limit: 100, nextToken: '', }) // Additional paginator properties // Whether there is a next page paginator.hasNext // Next page token paginator.nextToken // Fetch the next page await paginator.nextItems() ``` ```python Python highlight={5-6,13} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. paginator = Sandbox.list( limit=100, next_token="", ) paginator.has_next # Whether there is a next page paginator.next_token # Next page token # Fetch the next page paginator.next_items() ``` You can fetch all pages by looping through the paginator while checking if there is a next page (using `hasNext` or `has_next` property) and fetching until there are no more pages left to fetch: ```js JavaScript & TypeScript highlight={7} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const paginator = Sandbox.list() // Loop through all pages const sandboxes: SandboxInfo[] = [] while (paginator.hasNext) { const items = await paginator.nextItems() sandboxes.push(...items) } ``` ```python Python highlight={7} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, SandboxQuery paginator = Sandbox.list() # Loop through all pages sandboxes: list[SandboxInfo] = [] while paginator.has_next: items = paginator.next_items() sandboxes.extend(items) ``` ## Old SDK (v1.x.y) If you're using SDK with version lower than `2.0.0`, the `Sandbox.list()` method behaves differently. ```js JavaScript & TypeScript highlight={11} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create a sandbox. const sandbox = await Sandbox.create({ metadata: { name: 'My Sandbox', }, }) // List all running sandboxes. const runningSandboxes = await Sandbox.list() const runningSandbox = runningSandboxes[0] console.log('Running sandbox metadata:', runningSandbox.metadata) console.log('Running sandbox id:', runningSandbox.sandboxId) console.log('Running sandbox started at:', runningSandbox.startedAt) console.log('Running sandbox template id:', runningSandbox.templateId) ``` ```python Python highlight={11} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create a sandbox. sandbox = Sandbox.create( metadata: { name: 'My Sandbox', }, ) # List all running sandboxes. running_sandboxes = Sandbox.list() running_sandbox = running_sandboxes[0] print('Running sandbox metadata:', running_sandbox.metadata) print('Running sandbox id:', running_sandbox.sandbox_id) print('Running sandbox started at:', running_sandbox.started_at) print('Running sandbox template id:', running_sandbox.template_id) ``` ## Filtering sandboxes You can filter sandboxes by specifying [Metadata](/docs/sandbox/metadata) key value pairs. Specifying multiple key value pairs will return sandboxes that match all of them. This can be useful when you have a large number of sandboxes and want to find only specific ones. The filtering is performed on the server. ```js JavaScript & TypeScript highlight={6-8,15} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with metadata. const sandbox = await Sandbox.create({ metadata: { env: 'dev', app: 'my-app', userId: '123', }, }) // List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. const runningSandboxes = await Sandbox.list({ query: { metadata: { userId: '123', env: 'dev' }, }, }) ``` ```python Python highlight={7-9,17-18} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox from e2b.sandbox.sandbox_api import SandboxQuery # Create sandbox with metadata. sandbox = Sandbox.create( metadata={ "env": "dev", "app": "my-app", "user_id": "123", }, ) # List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`. running_sandboxes = Sandbox.list( query=SandboxQuery( metadata={ "userId": "123", "env": "dev", } ), ) ``` # Sandbox metadata Source: https://e2b.mintlify.app/docs/sandbox/metadata Metadata is a way to attach arbitrary key-value pairs for a sandbox. This is useful in various scenarios, for example: * Associate a sandbox with a user session. * Store custom user data for a sandbox like API keys. * Associate a sandbox with a user ID and [connect to it later](/docs/sandbox/connect). You specify metadata when creating a sandbox and can access it later through listing running sandboxes with `Sandbox.list()` method. ```js JavaScript & TypeScript highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create sandbox with metadata. const sandbox = await Sandbox.create({ metadata: { userId: '123', }, }) // List running sandboxes and access metadata. const paginator = await Sandbox.list() const runningSandboxes = await paginator.nextItems() // Will print: // { // 'userId': '123', // } console.log(runningSandboxes[0].metadata) ``` ```python Python highlight={6} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create sandbox with metadata. sandbox = Sandbox.create( metadata={ 'userId': '123', }, ) # List running sandboxes and access metadata. paginator = Sandbox.list() running_sandboxes = paginator.next_items() # Will print: # { # 'userId': '123', # } print(running_sandboxes[0].metadata) ``` ## Filtering sandboxes by metadata You can also filter sandboxes by metadata, you can find more about it [here](/docs/sandbox/list#filtering-sandboxes). # Sandbox metrics Source: https://e2b.mintlify.app/docs/sandbox/metrics The sandbox metrics allows you to get information about the sandbox's CPU, memory and disk usage. ## Getting sandbox metrics Getting the metrics of a sandbox returns an array of timestamped metrics containing CPU, memory and disk usage information. The metrics are collected every 5 seconds. ### Getting sandbox metrics using the SDKs ```js JavaScript & TypeScript highlight={9} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() console.log('Sandbox created', sbx.sandboxId) // Wait for a few seconds to collect some metrics await new Promise((resolve) => setTimeout(resolve, 10_000)) const metrics = await sbx.getMetrics() // You can also get the metrics by sandbox ID: // const metrics = await Sandbox.getMetrics(sbx.sandboxId) console.log('Sandbox metrics:', metrics) // Sandbox metrics: // [ // { // timestamp: 2025-07-28T08:04:05.000Z, // cpuUsedPct: 20.33, // cpuCount: 2, // memUsed: 32681984, // in bytes // memTotal: 507592704, // in bytes // diskUsed: 1514856448, // in bytes // diskTotal: 2573185024 // in bytes // }, // { // timestamp: 2025-07-28T08:04:10.000Z, // cpuUsedPct: 0.2, // cpuCount: 2, // memUsed: 33316864, // in bytes // memTotal: 507592704, // in bytes // diskUsed: 1514856448, // in bytes // diskTotal: 2573185024 // in bytes // } // ] ``` ```python Python highlight={10} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from time import sleep from e2b import Sandbox sbx = Sandbox.create() print('Sandbox created', sbx.sandbox_id) # Wait for a few seconds to collect some metrics sleep(10) metrics = sbx.get_metrics() # You can also get the metrics by sandbox ID: # metrics = Sandbox.get_metrics(sbx.sandbox_id) print('Sandbox metrics', metrics) # Sandbox metrics # [ # SandboxMetric( # cpu_count=2, # cpu_used_pct=13.97, # disk_total=2573185024, # in bytes # disk_used=1514856448, # in bytes # mem_total=507592704, # in bytes # mem_used=30588928, # in bytes # timestamp=datetime.datetime(2025, 7, 28, 8, 8, 15, tzinfo=tzutc()), # ), # SandboxMetric( # cpu_count=2, # cpu_used_pct=0.1, # disk_total=2573185024, # in bytes # disk_used=1514856448, # in bytes # mem_total=507592704, # in bytes # mem_used=31084544, # in bytes # timestamp=datetime.datetime(2025, 7, 28, 8, 8, 20, tzinfo=tzutc()), # ), # ] ``` ### Getting sandbox metrics using the CLI ```bash Terminal highlight={1} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b sandbox metrics # Metrics for sandbox # # [2025-07-25 14:05:55Z] CPU: 8.27% / 2 Cores | Memory: 31 / 484 MiB | Disk: 1445 / 2453 MiB # [2025-07-25 14:06:00Z] CPU: 0.5% / 2 Cores | Memory: 32 / 484 MiB | Disk: 1445 / 2453 MiB # [2025-07-25 14:06:05Z] CPU: 0.1% / 2 Cores | Memory: 32 / 484 MiB | Disk: 1445 / 2453 MiB # [2025-07-25 14:06:10Z] CPU: 0.3% / 2 Cores | Memory: 32 / 484 MiB | Disk: 1445 / 2453 MiB ``` It may take a second or more to get the first metrics after the sandbox is created. Until the first metrics are collected from the sandbox, you will get an empty array. # OTel telemetry export Source: https://e2b.mintlify.app/docs/sandbox/otel-telemetry-export E2B can export OpenTelemetry (OTel) metrics and logs to any OpenTelemetry-compatible destination with an OTLP HTTP endpoint. This lets you integrate E2B telemetry with your existing monitoring and observability stack. ## Exported telemetry Exported telemetry mirrors the metrics and logs available in the E2B dashboard. * Metrics are emitted under the `e2b.*` namespace. * Logs include `service_name: e2b`. Metrics include sandbox resource usage, such as CPU and memory usage. These metrics help you monitor sandbox health and capacity from your existing observability tools. Logs include sandbox lifecycle events, such as sandbox start and stop events, along with other sandbox actions. These logs help you correlate sandbox activity with application behavior and infrastructure events. E2B sends telemetry using OTLP over HTTP/protobuf. Your endpoint should accept OTLP HTTP metrics and logs traffic, such as `/v1/metrics` and `/v1/logs` paths on an OpenTelemetry Collector. Refer to your provider's documentation for specific OTLP endpoint details and authentication requirements. ## Setup During onboarding, provide E2B with the endpoint details for your OTel collector or observability backend: * OTLP HTTP endpoint URL * Authentication requirements, such as static headers or bearer tokens For BYOC deployments, E2B can configure telemetry export from your BYOC environment to your own collector or backend. ## Delivery behavior Telemetry export is best effort. E2B tries to send metrics and logs continuously and retries whenever possible. If your endpoint is unavailable, unreachable, or rejects telemetry, some metrics or logs may be delayed or dropped. ## Confirm telemetry delivery After E2B enables the export, start one or more sandboxes and wait a few minutes for telemetry to reach your destination. In your collector or observability backend, confirm that: * Metrics are emitted under the `e2b.*` namespace. * Logs include `service_name: e2b`. You can compare the received telemetry with the metrics and logs shown in the E2B dashboard. If telemetry does not appear, verify the OTLP endpoint URL, authentication headers, and any networking requirements such as allowlists or private connectivity. # Sandbox persistence Source: https://e2b.mintlify.app/docs/sandbox/persistence The sandbox persistence allows you to pause your sandbox and resume it later from the same state it was in when you paused it. This includes not only state of the sandbox's filesystem but also the sandbox's memory. This means all running processes, loaded variables, data, etc. ## Sandbox state transitions Understanding how sandboxes transition between different states is crucial for managing their lifecycle effectively. Here's a diagram showing the possible state transitions: ```mermaid actions={false} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} flowchart TD start(( )) -->|Sandbox.create| Running Running["Running
• Active execution
• Consumes resources"] Paused["Paused
• Preserves memory and files
• Cannot execute code"] Snapshotting["Snapshotting
• Creates persistent snapshot
• Briefly pauses execution"] Killed["Killed
• Resources released
• Cannot be resumed"] Running -->|pause| Paused Running -->|createSnapshot| Snapshotting Paused -->|connect| Running Snapshotting -->|snapshot complete| Running Running -->|kill| Killed Paused -->|kill| Killed ``` ### State descriptions * **Running**: The sandbox is actively running and can execute code. This is the initial state after creation. * **Paused**: The sandbox execution is suspended but its state is preserved. * **Snapshotting**: The sandbox is briefly paused while a persistent snapshot is being created. It automatically returns to Running. See [Snapshots](/docs/sandbox/snapshots). * **Killed**: The sandbox is terminated and all resources are released. This is a terminal state. ### Changing sandbox's state ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Starts in Running state // Pause the sandbox await sandbox.pause() // Running → Paused // Resume the sandbox await sandbox.connect() // Running/Paused → Running // Kill the sandbox (from any state) await sandbox.kill() // Running/Paused → Killed ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Starts in Running state # Pause the sandbox sandbox.pause() # Running → Paused # Resume the sandbox sandbox.connect() # Running/Paused → Running # Kill the sandbox (from any state) sandbox.kill() # Running/Paused → Killed ``` ## Pausing sandbox When you pause a sandbox, both the sandbox's filesystem and memory state will be saved. This includes all the files in the sandbox's filesystem and all the running processes, loaded variables, data, etc. ```js JavaScript & TypeScript highlight={8-9} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() console.log('Sandbox created', sbx.sandboxId) // Pause the sandbox // You can save the sandbox ID in your database to resume the sandbox later await sbx.pause() console.log('Sandbox paused', sbx.sandboxId) ``` ```python Python highlight={8-9} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() print('Sandbox created', sbx.sandbox_id) # Pause the sandbox # You can save the sandbox ID in your database to resume the sandbox later sbx.pause() print('Sandbox paused', sbx.sandbox_id) ``` By default a pause saves **both** the filesystem and the memory. To save only the filesystem — a lighter snapshot that cold-boots (reboots) on resume — pass `keepMemory: false` (JavaScript) / `keep_memory=False` (Python). See [Filesystem-only snapshots](/docs/sandbox/filesystem-only-snapshots). ## Resuming sandbox When you resume a sandbox, it will be in the same state it was in when you paused it. This means that all the files in the sandbox's filesystem will be restored and all the running processes, loaded variables, data, etc. will be restored. ```js JavaScript & TypeScript highlight={12-13} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() console.log('Sandbox created', sbx.sandboxId) // Pause the sandbox // You can save the sandbox ID in your database to resume the sandbox later await sbx.pause() console.log('Sandbox paused', sbx.sandboxId) // Connect to the sandbox (it will automatically resume the sandbox, if paused) const sameSbx = await sbx.connect() console.log('Connected to the sandbox', sameSbx.sandboxId) ``` ```python Python highlight={12-13} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() print('Sandbox created', sbx.sandbox_id) # Pause the sandbox # You can save the sandbox ID in your database to resume the sandbox later sbx.pause() print('Sandbox paused', sbx.sandbox_id) # Connect to the sandbox (it will automatically resume the sandbox, if paused) same_sbx = sbx.connect() print('Connected to the sandbox', same_sbx.sandbox_id) ``` ## Listing paused sandboxes You can list all paused sandboxes by calling the `Sandbox.list` method and supplying the `state` query parameter. More information about using the method can be found in [List Sandboxes](/docs/sandbox/list). ```js JavaScript & TypeScript highlight={4,7} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox, SandboxInfo } from 'e2b' // List all paused sandboxes const paginator = Sandbox.list({ query: { state: ['paused'] } }) // Get the first page of paused sandboxes const sandboxes = await paginator.nextItems() // Get all paused sandboxes while (paginator.hasNext) { const items = await paginator.nextItems() sandboxes.push(...items) } ``` ```python Python highlight={4,7} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # List all paused sandboxes from e2b import Sandbox, SandboxQuery, SandboxState paginator = Sandbox.list(SandboxQuery(state=[SandboxState.PAUSED])) # Get the first page of paused sandboxes sandboxes = paginator.next_items() # Get all paused sandboxes while paginator.has_next: items = paginator.next_items() sandboxes.extend(items) ``` ## Removing paused sandboxes You can remove paused sandboxes by calling the `kill` method on the Sandbox instance. ```js JavaScript & TypeScript highlight={11,14} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create() console.log('Sandbox created', sbx.sandboxId) // Pause the sandbox // You can save the sandbox ID in your database to resume the sandbox later await sbx.pause() // Remove the sandbox await sbx.kill() // Remove sandbox by id await Sandbox.kill(sbx.sandboxId) ``` ```python Python highlight={9,12} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create() # Pause the sandbox sbx.pause() # Remove the sandbox sbx.kill() # Remove sandbox by id Sandbox.kill(sbx.sandbox_id) ``` ## Sandbox's timeout When you connect to a sandbox, the timeout resets. The default is 5 minutes, but you can pass a custom timeout to the `Sandbox.connect()` method: ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.connect(sandboxId, { timeoutMs: 60 * 1000 }) // 60 seconds ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.connect(sandbox_id, timeout=60) # 60 seconds ``` ### Auto-pause Auto-pause is configured in the sandbox lifecycle on create. `onTimeout`/`on_timeout` defaults to `"kill"`, meaning the sandbox is terminated when its timeout expires. Set it to `"pause"` to auto-pause on timeout instead. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ timeoutMs: 10 * 60 * 1000, // Optional: change default timeout (10 minutes) lifecycle: { onTimeout: 'pause', // Defaults to 'kill'; set to 'pause' to auto-pause on timeout autoResume: false, // Optional (default is false) }, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create( timeout=10 * 60, # Optional: change default timeout (10 minutes) lifecycle={ "on_timeout": "pause", # Defaults to "kill"; set to "pause" to auto-pause on timeout "auto_resume": False, # Optional (default is False) }, ) ``` Auto-pause is persistent, meaning if your sandbox resumes and later times out again, it will pause again. To make the auto-pause filesystem-only — dropping memory so resume cold-boots — use the object form `onTimeout: { action: 'pause', keepMemory: false }` (JavaScript) / `"on_timeout": {"action": "pause", "keep_memory": False}` (Python). See [Filesystem-only snapshots](/docs/sandbox/filesystem-only-snapshots). If you call `.kill()`, the sandbox is permanently deleted and cannot be resumed. For auto-resume behavior, see [AutoResume](/docs/sandbox/auto-resume). ## Network If you have a service (for example a server) running inside your sandbox and you pause the sandbox, the service won't be accessible from the outside and all the clients will be disconnected. If you resume the sandbox, the service will be accessible again but you need to connect clients again. ## Limitations ### Pause and resume performance * Pausing a sandbox takes approximately **4 seconds per 1 GiB of RAM** * Resuming a sandbox takes approximately **1 second** ### Paused sandbox retention * Paused sandboxes are kept **indefinitely**; there is no automatic deletion or time-to-live limit * There is currently **no configurable "auto-kill after N days" option**; a paused sandbox will not expire on its own * You can resume a paused sandbox at any time * To remove a paused sandbox, you must kill it explicitly with an API call (`sandbox.kill()` / `Sandbox.kill(sandboxId)`), as shown in [Removing paused sandboxes](#removing-paused-sandboxes) ### Continuous runtime limits * A sandbox can remain running (without being paused) for: * **24 hours** on the **Pro tier** * **1 hour** on the **Hobby tier** * After a sandbox is paused and resumed, the continuous runtime limit is **reset** # Interactive terminal (PTY) Source: https://e2b.mintlify.app/docs/sandbox/pty The PTY (pseudo-terminal) module allows you to create interactive terminal sessions in the sandbox with real-time, bidirectional communication. Unlike `commands.run()` which executes a command and returns output after completion, PTY provides: * **Real-time streaming** - Output is streamed as it happens via callbacks * **Bidirectional input** - Send input while the terminal is running * **Interactive shell** - Full terminal support with ANSI colors and escape sequences * **Session persistence** - Disconnect and reconnect to running sessions ## Create a PTY session Use `sandbox.pty.create()` to start an interactive bash shell. This example runs `echo 'hello world'`, then exits and prints the terminal output. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const terminal = await sandbox.pty.create({ cols: 80, // Terminal size in characters rows: 24, envs: { MY_VAR: 'hello' }, // Optional environment variables cwd: '/home/user', // Optional working directory user: 'root', // Optional user to run as onData: (data) => { // Called whenever the terminal outputs data process.stdout.write(data) }, }) // terminal.pid contains the process ID console.log('Terminal PID:', terminal.pid) // Send input to the PTY. Data must be bytes (Uint8Array), and the trailing newline "presses Enter". await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode("echo 'hello world'\n")) // The PTY runs an interactive login shell that won't exit on its own, so tell it // to exit — otherwise wait() below blocks forever. await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n')) // Wait for the terminal to exit (output is streamed to onData above) await terminal.wait() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, PtySize sandbox = Sandbox.create() terminal = sandbox.pty.create( size=PtySize(rows=24, cols=80), # Terminal size in characters envs={'MY_VAR': 'hello'}, # Optional environment variables cwd='/home/user', # Optional working directory user='root', # Optional user to run as ) # terminal.pid contains the process ID print('Terminal PID:', terminal.pid) # Send input to the PTY via the pty module. Data must be bytes, and the trailing newline "presses Enter". sandbox.pty.send_stdin(terminal.pid, b"echo 'hello world'\n") # The PTY runs an interactive login shell that won't exit on its own, so tell it # to exit — otherwise wait() below blocks forever. sandbox.pty.send_stdin(terminal.pid, b"exit\n") # Stream output by passing on_pty to wait() # end='' prevents print from adding an extra newline terminal.wait(on_pty=lambda data: print(data.decode(), end='')) ``` The PTY runs an interactive bash shell with `TERM=xterm-256color`, which supports ANSI colors and escape sequences. ## Timeout PTY sessions have a configurable timeout that controls the session duration. The default is 60 seconds. For interactive or long-running sessions, set `timeoutMs: 0` (JavaScript) or `timeout=0` (Python) to keep the session open indefinitely. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const terminal = await sandbox.pty.create({ cols: 80, rows: 24, onData: (data) => process.stdout.write(data), timeoutMs: 0, // Keep the session open indefinitely }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, PtySize sandbox = Sandbox.create() terminal = sandbox.pty.create( PtySize(rows=24, cols=80), timeout=0, # Keep the session open indefinitely ) # end='' prevents print() from adding an extra newline # (PTY output already contains newlines) terminal.wait(on_pty=lambda data: print(data.decode(), end='')) ``` ## Send input to PTY Use `sendInput()` in JavaScript or `send_stdin()` in Python to send data to the terminal. These methods return a Promise (JavaScript) or complete synchronously (Python) - the actual output is delivered to your `onData` callback (JavaScript) or to the `on_pty` callback you pass to `wait()` (Python). ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const terminal = await sandbox.pty.create({ cols: 80, rows: 24, onData: (data) => process.stdout.write(data), }) // Send a command (don't forget the newline!) await sandbox.pty.sendInput( terminal.pid, new TextEncoder().encode('echo "Hello from PTY"\n') ) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, PtySize sandbox = Sandbox.create() terminal = sandbox.pty.create(PtySize(rows=24, cols=80)) # Send a command as bytes (b'...' is Python's byte string syntax) # Don't forget the newline! sandbox.pty.send_stdin(terminal.pid, b'echo "Hello from PTY"\n') # Receive output by passing on_pty to wait() # end='' prevents print from adding an extra newline terminal.wait(on_pty=lambda data: print(data.decode(), end='')) ``` ## Resize the terminal When the user's terminal window changes size, notify the PTY with `resize()`. The `cols` and `rows` parameters are measured in characters, not pixels. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const terminal = await sandbox.pty.create({ cols: 80, rows: 24, onData: (data) => process.stdout.write(data), }) // Resize to new dimensions (in characters) await sandbox.pty.resize(terminal.pid, { cols: 120, rows: 40, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, PtySize sandbox = Sandbox.create() terminal = sandbox.pty.create(PtySize(rows=24, cols=80)) # Resize to new dimensions (in characters) sandbox.pty.resize(terminal.pid, PtySize(rows=40, cols=120)) ``` ## Disconnect and reconnect You can disconnect from a PTY session while keeping it running, then reconnect later with a new data handler. This is useful for: * Resuming terminal sessions after network interruptions * Sharing terminal access between multiple clients * Implementing terminal session persistence ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Create a PTY session const terminal = await sandbox.pty.create({ cols: 80, rows: 24, onData: (data) => console.log('Handler 1:', new TextDecoder().decode(data)), }) const pid = terminal.pid // Send a command await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo hello\n')) // Disconnect - PTY keeps running in the background await terminal.disconnect() // Later: reconnect with a new data handler const reconnected = await sandbox.pty.connect(pid, { onData: (data) => console.log('Handler 2:', new TextDecoder().decode(data)), }) // Continue using the session await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo world\n')) // Wait for the terminal to exit await reconnected.wait() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, PtySize sandbox = Sandbox.create() # Create a PTY session terminal = sandbox.pty.create(PtySize(rows=24, cols=80)) pid = terminal.pid # Send a command sandbox.pty.send_stdin(pid, b'echo hello\n') # Disconnect - PTY keeps running in the background terminal.disconnect() # Later: reconnect to the same session reconnected = sandbox.pty.connect(pid) # Continue using the session, then exit sandbox.pty.send_stdin(pid, b'echo world\nexit\n') # Wait for the terminal to exit, streaming output via on_pty reconnected.wait(on_pty=lambda data: print('Handler:', data.decode())) ``` ## Kill the PTY Terminate the PTY session with `kill()`. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const terminal = await sandbox.pty.create({ cols: 80, rows: 24, onData: (data) => process.stdout.write(data), }) // Kill the PTY const killed = await sandbox.pty.kill(terminal.pid) console.log('Killed:', killed) // true if successful // Or use the handle method // await terminal.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, PtySize sandbox = Sandbox.create() terminal = sandbox.pty.create(PtySize(rows=24, cols=80)) # Kill the PTY killed = sandbox.pty.kill(terminal.pid) print('Killed:', killed) # True if successful # Or use the handle method # terminal.kill() ``` ## Wait for PTY to exit Use `wait()` to wait for the terminal session to end (e.g., when the user types `exit`). ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() const terminal = await sandbox.pty.create({ cols: 80, rows: 24, onData: (data) => process.stdout.write(data), }) // Send exit command await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n')) // Wait for the terminal to exit const result = await terminal.wait() console.log('Exit code:', result.exitCode) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox, PtySize sandbox = Sandbox.create() terminal = sandbox.pty.create(PtySize(rows=24, cols=80)) # Send exit command sandbox.pty.send_stdin(terminal.pid, b'exit\n') # Wait for the terminal to exit, streaming output via on_pty result = terminal.wait(on_pty=lambda data: print(data.decode(), end='')) print('Exit code:', result.exit_code) ``` ## Interactive terminal (SSH-like) Building a fully interactive terminal (like SSH) requires handling raw mode, stdin forwarding, and terminal resize events. For a production implementation, see the [E2B CLI source code](https://github.com/e2b-dev/E2B/blob/main/packages/cli/src/terminal.ts) which uses the same `sandbox.pty` API documented above. # Secured access Source: https://e2b.mintlify.app/docs/sandbox/secured-access Secure access authenticates communication between SDK and sandbox controller. Sandbox controller runs in sandbox itself and exposes APIs for work with file system, run commands, and generally control the sandbox via our SDK. Without secure access, anyone with a sandbox ID can access the controller APIs and control the sandbox from inside. SDKs version `v2.0.0` and above are using secure access by default when creating sandbox. This may not be compatible with older custom templates and you may need to rebuild them. ## Migration path When using custom templates created before envd `v0.2.0`, you need to rebuild the templates to enable secure access. Temporarily, you can disable secure access by setting `secure` to `false` during sandbox creation, but this is not recommended for production use because it increases security risks. You can check the template envd version using the `e2b template list` command or by viewing the templates list on the dashboard. ## Supported versions All sandboxes based on templates with envd version at least `v0.2.0` already support secure access without any additional changes. The secure access flag was introduced in `1.5.0` for JavaScript and Python SDKs to be used optionally. Starting with SDK version `v2.0.0`, sandboxes are created with secure access enabled by default. ## Access sandbox API directly In some cases, you might want to access sandbox controller APIs directly through its URL, such as when you are not using SDKs. When secure access is enabled, you must provide an authentication token that was returned during sandbox creation. Each call to the sandbox controller must include an additional header `X-Access-Token` with the access token value returned during sandbox creation. For sandbox [upload](/docs/filesystem/upload#upload-with-pre-signed-url) and [download](/docs/filesystem/download#download-with-pre-signed-url) URLs, you need to generate pre-signed URLs. We are advising to use SDK for generating presigned URLs. ## Disable secure access Disabling secured access is discouraged because it creates security vulnerabilities. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ secure: false }) // Explicitly disable ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create(secure=False) # Explicitly disable ``` # Sandbox snapshots Source: https://e2b.mintlify.app/docs/sandbox/snapshots Snapshots let you create a persistent point-in-time capture of a running sandbox, including both its filesystem and memory state. You can then use a snapshot to spawn new sandboxes that start from the exact same state. The original sandbox continues running after the snapshot is created, and a single snapshot can be used to create many new sandboxes. ## Prerequisites Snapshots require templates with envd version `v0.5.0` or above. If you are using a custom template created before envd `v0.5.0`, you need to rebuild it. You can check the template envd version using the `e2b template list` command or by viewing the templates list on the dashboard. ## Snapshots vs. Pause/Resume | | Pause/Resume | Snapshots | | -------------------------- | --------------------------------------------- | --------------------------------------------------- | | Effect on original sandbox | Pauses (stops) the sandbox | Sandbox briefly pauses, then continues running | | Relationship | One-to-one — resume restores the same sandbox | One-to-many — snapshot can spawn many new sandboxes | | Use case | Suspend and resume a single sandbox | Create a reusable checkpoint | For pause/resume functionality, see [Persistence](/docs/sandbox/persistence). ## Snapshot flow ```mermaid actions={false} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} graph LR A[Running Sandbox] -->|createSnapshot| B[Snapshotting] B --> C[Snapshot Created] B --> A C -->|Sandbox.create| D[New Sandbox 1] C -->|Sandbox.create| E[New Sandbox 2] C -->|Sandbox.create| F[New Sandbox N] ``` The sandbox is briefly paused during the snapshot process but automatically returns to running state. The sandbox ID stays the same after the snapshot completes. During the snapshot, the sandbox is temporarily paused and resumed. This causes all active connections (e.g. WebSocket, PTY, command streams) to be dropped. Make sure your client handles reconnection properly. ## Create a snapshot You can create a snapshot from a running sandbox instance. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create() // Create a snapshot from a running sandbox const snapshot = await sandbox.createSnapshot() console.log('Snapshot ID:', snapshot.snapshotId) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create() # Create a snapshot from a running sandbox snapshot = sandbox.create_snapshot() print('Snapshot ID:', snapshot.snapshot_id) ``` You can also create a snapshot by sandbox ID using the static method. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Create a snapshot by sandbox ID const snapshot = await Sandbox.createSnapshot(sandboxId) console.log('Snapshot ID:', snapshot.snapshotId) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox # Create a snapshot by sandbox ID snapshot = Sandbox.create_snapshot(sandbox_id) print('Snapshot ID:', snapshot.snapshot_id) ``` ## Create a sandbox from a snapshot The snapshot ID can be used directly with `Sandbox.create()` to spawn a new sandbox from the snapshot. The new sandbox starts with the exact filesystem and memory state captured in the snapshot. ```js JavaScript & TypeScript highlight={5} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const snapshot = await sandbox.createSnapshot() // Create a new sandbox from the snapshot const newSandbox = await Sandbox.create(snapshot.snapshotId) ``` ```python Python highlight={5} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox snapshot = sandbox.create_snapshot() # Create a new sandbox from the snapshot new_sandbox = Sandbox.create(snapshot.snapshot_id) ``` ## List snapshots You can list all snapshots. The method returns a paginator for iterating through results. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const paginator = Sandbox.listSnapshots() const snapshots = [] while (paginator.hasNext) { const items = await paginator.nextItems() snapshots.push(...items) } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox paginator = Sandbox.list_snapshots() snapshots = [] while paginator.has_next: items = paginator.next_items() snapshots.extend(items) ``` ### Filter by sandbox You can filter snapshots created from a specific sandbox. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const paginator = Sandbox.listSnapshots({ sandboxId: 'your-sandbox-id' }) const snapshots = await paginator.nextItems() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox paginator = Sandbox.list_snapshots(sandbox_id="your-sandbox-id") snapshots = paginator.next_items() ``` ## Delete a snapshot ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' // Returns true if deleted, false if the snapshot was not found const deleted = await Sandbox.deleteSnapshot(snapshot.snapshotId) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox Sandbox.delete_snapshot(snapshot.snapshot_id) ``` ## Snapshots vs. Templates Both snapshots and [templates](/docs/template/quickstart) create reusable starting points for sandboxes, but they solve different problems. | | Templates | Snapshots | | --------------- | ---------------------------------------------------- | ---------------------------------------------- | | Defined by | Declarative code (Template builder) | Capturing a running sandbox | | Reproducibility | Same definition produces the same sandbox every time | Captures whatever state exists at that moment | | Best for | Repeatable base environments | Checkpointing, rollback, forking runtime state | Use templates when every sandbox should start from an identical, known state — pre-installed tools, fixed configurations, consistent environments. Use snapshots when you need to capture or fork live runtime state that depends on what happened during execution. ### Performance For workloads where either approach would work, **templates are faster and more resource-efficient** than snapshots: * **Compact memory.** During a template build, the guest OS is restarted before the long-running process is captured. Memory is compact and any setup-time processes that aren't needed at runtime are gone, so sandboxes start with less memory pressure and fewer resources. * **More effective prefetching.** E2B optimistically prefetches data needed to start a sandbox. For templates this prefetching is highly effective; for snapshots its effectiveness is significantly lower due to memory fragmentation and general memory pressure from the captured live state. If you can express the state you need as a declarative template build, you'll generally get faster cold starts and lower overhead than capturing the equivalent state as a snapshot. Creating many templates is a supported pattern, so this doesn't need to discourage you from using templates per-customer or per-project. See [Scaling templates](/docs/template/quickstart#scaling-templates). ## Use cases * **Checkpointing agent work** — an AI agent has loaded data and produced partial results in memory. Snapshot it so you can resume or fork from that point later. * **Rollback points** — snapshot before a risky or expensive operation (running untrusted code, applying a migration, refactoring a web app). If it fails, rollback - spawn a fresh sandbox from the snapshot before the operation happened. * **Forking workflows** — spawn multiple sandboxes from the same snapshot to explore different approaches in parallel. * **Cached sandboxes** — avoid repeating expensive setup by snapshotting a sandbox that has already loaded a large dataset or started a long-running process. * **Sharing state** — one user or agent configures an environment interactively, snapshots it, and others start from that exact state. # SSH access Source: https://e2b.mintlify.app/docs/sandbox/ssh-access Connect to your sandbox via SSH using a WebSocket proxy SSH access enables remote terminal sessions, SCP/SFTP file transfers, and integration with tools that expect SSH connectivity. ## Quickstart Define a template with OpenSSH server and [websocat](https://github.com/vi/websocat): ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template, waitForPort } from 'e2b' export const template = Template() .fromUbuntuImage('25.04') .aptInstall(['openssh-server']) .runCmd([ 'curl -fsSL -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl', 'chmod a+x /usr/local/bin/websocat', ], { user: 'root' }) .setStartCmd('sudo websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22', waitForPort(8081)) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template, wait_for_port template = ( Template() .from_ubuntu_image("25.04") .apt_install(["openssh-server"]) .run_cmd([ "curl -fsSL -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl", "chmod a+x /usr/local/bin/websocat", ], user="root") .set_start_cmd("sudo websocat -b --exit-on-eof ws-l:0.0.0.0:8081 tcp:127.0.0.1:22", wait_for_port(8081)) ) ``` Build the template: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as sshTemplate } from './template' await Template.build(sshTemplate, 'ssh-ready', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from template import template as ssh_template Template.build(ssh_template, "ssh-ready", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sbx = await Sandbox.create('ssh-ready') console.log(sbx.sandboxId) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sbx = Sandbox.create("ssh-ready") print(sbx.sandbox_id) ``` ```bash macOS theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Install websocat brew install websocat # Connect to your sandbox ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' user@ ``` ```bash Linux theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Install websocat sudo curl -fsSL -o /usr/local/bin/websocat https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl sudo chmod a+x /usr/local/bin/websocat # Connect to your sandbox ssh -o 'ProxyCommand=websocat --binary -B 65536 - wss://8081-%h.e2b.app' user@ ``` *** ## How it works This method uses [websocat](https://github.com/vi/websocat) to proxy SSH connections over WebSocket through the sandbox's exposed ports. ``` ┌───────────────────────────────────────────────────────────┐ │ Your Machine │ │ ┌──────────┐ ProxyCommand ┌──────────────────┐ │ │ │ SSH │ ────────────────── │ websocat │ │ │ │ Client │ │ (WebSocket) │ │ │ └──────────┘ └─────────┬────────┘ │ └────────────────────────────────────────────┼──────────────┘ │ wss://8081-.e2b.app │ ┌────────────────────────────────────────────┼──────────────┐ │ E2B Sandbox ▼ │ │ ┌──────────────────┐ │ │ │ websocat │ │ │ │ (WS → TCP:22) │ │ │ └─────────┬────────┘ │ │ │ │ │ ┌─────────▼────────┐ │ │ │ SSH Server │ │ │ │ (OpenSSH) │ │ │ └──────────────────┘ │ └───────────────────────────────────────────────────────────┘ ``` # Archil Source: https://e2b.mintlify.app/docs/storage/archil Mount Archil's elastic POSIX file system in your E2B sandbox for high-performance, shared access to S3, R2, and GCS object storage. [Archil](https://archil.com) is an infinite, elastic, POSIX file system that automatically synchronizes to object storage like S3, R2, and GCS. Use Archil when you need to mount a bucket to your E2B sandbox but want higher out-of-the-box performance than `s3fs` or `gcsfuse` — it's a natural fit for parallel agents that share state. Key properties: * Shared SSD read and write caching in front of your object storage bucket for higher performance. * Mounted as a regular POSIX directory, so any tool, script, or agent in the sandbox can use it. * Scales to whatever your sandbox writes — you pay only for what you use. * [Mountable by many sandboxes at once](https://docs.archil.com/concepts/shared-disks) for shared state across parallel agents. ## Build a template Build a template with the `archil` CLI preinstalled. The Archil package depends on `libfuse2`, which is already preinstalled in the base image. ```ts JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const template = Template() .fromBaseImage() .runCmd("curl -fsSL https://archil.com/install | sh") ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template = ( Template() .from_base_image() .run_cmd("curl -fsSL https://archil.com/install | sh") ) ``` ## Mount the disk At sandbox runtime, mount the disk with a [disk-scoped mount token](https://docs.archil.com/concepts/disk-users#disk-token-authorization) from the [Archil console](https://console.archil.com). ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('') await sandbox.commands.run('mkdir -p /home/user/archil', { user: 'root' }) await sandbox.commands.run( 'archil mount /home/user/archil --region ', { user: 'root', envs: { ARCHIL_MOUNT_TOKEN: '' } } ) await sandbox.commands.run('chown user:user /home/user/archil', { user: 'root' }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create("") sandbox.commands.run("mkdir -p /home/user/archil", user="root") sandbox.commands.run( "archil mount /home/user/archil --region ", user="root", envs={"ARCHIL_MOUNT_TOKEN": ""}, ) sandbox.commands.run("chown user:user /home/user/archil", user="root") ``` `` is either an owner-qualified name (`myorg/my-disk`) or a disk ID (`dsk-0123456789abcdef`); `` is the disk's region (e.g. `aws-us-east-1`). The disk is now mounted at `/home/user/archil` as a regular POSIX directory — any tool, script, or agent in the sandbox can read and write through it. ## Unmount before killing the sandbox Call `archil unmount` before tearing down the sandbox. It's best practice to release filesystem resources cleanly rather than letting the sandbox kill take them down. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} await sandbox.commands.run('archil unmount /home/user/archil', { user: 'root' }) await sandbox.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} sandbox.commands.run("archil unmount /home/user/archil", user="root") sandbox.kill() ``` ## Sharing across sandboxes The same disk can be mounted by multiple sandboxes concurrently. Add `--shared` to the mount command — see [Shared Disks](https://docs.archil.com/concepts/shared-disks) for the checkout-based concurrency model. # Cloud buckets Source: https://e2b.mintlify.app/docs/storage/cloud-buckets To connect a bucket for storing data from the sandbox, we will use the FUSE file system to mount the bucket to the sandbox. You will need to create a custom sandbox template with the FUSE file system installed. The guide for building a custom sandbox template can be found [here](/docs/template/quickstart). ## Google Cloud Storage ### Prerequisites To use Google Cloud Storage, you'll need a bucket and a service account. You can create a service account [here](https://console.cloud.google.com/iam-admin/serviceaccounts) and a bucket [here](https://console.cloud.google.com/storage). If you want to write to the bucket, make sure the service account has the `Storage Object User` role for this bucket. You can find a guide on creating a service account key [here](https://cloud.google.com/iam/docs/keys-create-delete#iam-service-account-keys-create-console). ### Mounting the bucket To use the Google Cloud Storage we need to install the `gcsfuse` package. There's simple [template](/docs/template/quickstart) that can be used to create a container with the `gcsfuse` installed. ```ts JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const template = Template() .fromTemplate("code-interpreter-v1") .aptInstall(["gnupg", "lsb-release"]) .runCmd("lsb_release -c -s > /tmp/lsb_release") .runCmd( 'GCSFUSE_REPO=$(cat /tmp/lsb_release) && echo "deb [signed-by=/usr/share/keyrings/cloud.google.asc] https://packages.cloud.google.com/apt gcsfuse-$GCSFUSE_REPO main" | sudo tee /etc/apt/sources.list.d/gcsfuse.list', ) .runCmd( "curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo tee /usr/share/keyrings/cloud.google.asc", ) .aptInstall(["gcsfuse"]) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template = ( Template() .from_template("code-interpreter-v1") .apt_install(["gnupg", "lsb-release"]) .run_cmd("lsb_release -c -s > /tmp/lsb_release") .run_cmd( 'GCSFUSE_REPO=$(cat /tmp/lsb_release) && echo "deb [signed-by=/usr/share/keyrings/cloud.google.asc] https://packages.cloud.google.com/apt gcsfuse-$GCSFUSE_REPO main" | sudo tee /etc/apt/sources.list.d/gcsfuse.list' ) .run_cmd( "curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo tee /usr/share/keyrings/cloud.google.asc" ) .apt_install(["gcsfuse"]) ) ``` The bucket is mounted during the sandbox runtime using the `gcsfuse` command. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('') await sandbox.files.makeDir('/home/user/bucket') await sandbox.files.write('key.json', '') await sandbox.commands.run('sudo gcsfuse --key-file /home/user/key.json /home/user/bucket') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create("") sandbox.files.make_dir("/home/user/bucket") sandbox.files.write("key.json", "") output = sandbox.commands.run( "sudo gcsfuse --key-file /home/user/key.json /home/user/bucket" ) ``` ### Flags The complete list of flags is available [here](https://cloud.google.com/storage/docs/gcsfuse-cli#options). ### Allow the default user to access the files To allow the default user to access the files, we can use the following flags: ``` -o allow_other -file-mode=777 -dir-mode=777 ``` ## Amazon S3 To use Amazon S3, we can use the `s3fs` package. The [template](/docs/template/quickstart) setup is similar to that of Google Cloud Storage. ```ts JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const template = Template() .fromImage("ubuntu:latest") .aptInstall(["s3fs"]) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template = ( Template() .from_image("ubuntu:latest") .apt_install(["s3fs"]) ) ``` Similar to Google Cloud Storage, the bucket is mounted during the runtime of the sandbox. The `s3fs` command is used to mount the bucket to the sandbox. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create('') await sandbox.files.makeDir('/home/user/bucket') // Create a file with the credentials // If you use another path for the credentials you need to add the path in the command s3fs command await sandbox.files.write('/root/.passwd-s3fs', ':') await sandbox.commands.run('sudo chmod 600 /root/.passwd-s3fs') await sandbox.commands.run('sudo s3fs /home/user/bucket') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create("") sandbox.files.make_dir("/home/user/bucket") # Create a file with the credentials # If you use another path for the credentials you need to add the path in the command s3fs command sandbox.files.write("/root/.passwd-s3fs", ":") sandbox.commands.run("sudo chmod 600 /root/.passwd-s3fs") sandbox.commands.run("sudo s3fs /home/user/bucket") ``` ### Flags The complete list of flags is available [here](https://manpages.ubuntu.com/manpages/xenial/man1/s3fs.1.html). ### Allow the default user to access the files To allow the default user to access the files, add the following flag: ``` -o allow_other ``` ## Cloudflare R2 For Cloudflare R2, we can use a setup very similar to S3. The template remains the same as for S3. However, the mounting differs slightly; we need to specify the endpoint for R2. ```js JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Sandbox } from 'e2b' const sandbox = await Sandbox.create({ template: '' }) await sandbox.files.makeDir('/home/user/bucket') // Create a file with the R2 credentials // If you use another path for the credentials you need to add the path in the command s3fs command await sandbox.files.write('/root/.passwd-s3fs', ':') await sandbox.commands.run('sudo chmod 600 /root/.passwd-s3fs') await sandbox.commands.run('sudo s3fs -o url=https://.r2.cloudflarestorage.com /home/user/bucket') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Sandbox sandbox = Sandbox.create("") sandbox.files.make_dir("/home/user/bucket") # Create a file with the R2 credentials # If you use another path for the credentials you need to add the path in the command s3fs command sandbox.files.write("/root/.passwd-s3fs", ":") sandbox.commands.run("sudo chmod 600 /root/.passwd-s3fs") sandbox.commands.run( "sudo s3fs -o url=https://.r2.cloudflarestorage.com /home/user/bucket" ) ``` ### Flags It's the same as for S3. # Need help? Source: https://e2b.mintlify.app/docs/support The fastest way to reach our support team is through the **Contact Support** button in your [E2B Dashboard](https://e2b.dev/dashboard?support=true). You'll find it in the bottom-left corner of the dashboard. Open the E2B Dashboard to contact our support team directly. You can also reach us through these channels: # Base image Source: https://e2b.mintlify.app/docs/template/base-image How to define a base image for your template ## Creating template When creating a template, you can specify options: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const template = Template({ fileContextPath: ".", // Custom file context path fileIgnorePatterns: [".git", "node_modules"], // File patterns to ignore }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template = Template( file_context_path=".", # Custom file context path file_ignore_patterns=[".git", "node_modules"], # File patterns to ignore ) ``` **File ignoring**: The SDK automatically reads `.dockerignore` files and combines them with your `fileIgnorePatterns` (TypeScript) or `file_ignore_patterns` (Python). Files matching these patterns are excluded from uploads and hash calculations. ## Defining base image Every template starts with a base image that provides the foundation for your sandbox environment. E2B currently supports only Debian-based images. Your base image must be Debian or a Debian derivative (e.g., `debian`, `ubuntu`, `python`, `node`). Alpine, RedHat-based, and other non-Debian distributions are not supported. ### Predefined base images Use convenience methods for common base images with Ubuntu, Debian, Python, Node.js, or Bun: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.fromUbuntuImage("22.04"); // ubuntu:22.04 template.fromDebianImage("stable-slim"); // debian:stable-slim template.fromPythonImage("3.13"); // python:3.13 template.fromNodeImage("lts"); // node:lts template.fromBunImage("1.3"); // oven/bun:1.3 ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.from_ubuntu_image("22.04") # ubuntu:22.04 template.from_debian_image("stable-slim") # debian:stable-slim template.from_python_image("3.13") # python:3.13 template.from_node_image("lts") # node:lts template.from_bun_image("1.3") # oven/bun:1.3 ``` ### Custom base image Use any Docker image from Docker Hub or other registries: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.fromImage("custom-image:latest"); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.from_image("custom-image:latest") ``` ### Default E2B base image Use the default E2B base image, which comes pre-configured for sandbox environments: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.fromBaseImage(); // e2bdev/base ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.from_base_image() # e2bdev/base ``` `base` is also the template used when you call `Sandbox.create()` without specifying one. It is Debian-based and comes with common tooling pre-installed: Python 3, Node.js, Yarn, `git`, `curl`, `build-essential`, and the GitHub CLI (`gh`). For the exact contents, see the public Dockerfile: [`templates/base/e2b.Dockerfile`](https://github.com/e2b-dev/E2B/blob/main/templates/base/e2b.Dockerfile). ### Default resources A sandbox gets its CPU and memory from the template it's built on. A sandbox built from `base` starts with **2 vCPU and 512 MiB of RAM**. You can override CPU and memory when you [build a template](/docs/template/build) (`cpuCount`/`cpu_count`, `memoryMB`/`memory_mb`). Disk size is not a default you set per sandbox; it's determined by your team's tier limit and applied at build time (see [Build limits](/docs/template/quickstart#build-limits)). ### Build from existing template Extend an existing template from your team or organization: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.fromTemplate("my-template"); // Your team's template template.fromTemplate("acme/other-template"); // Full namespaced reference ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.from_template("my-template") # Your team's template template.from_template("acme/other-template") # Full namespaced reference ``` You can only call base image methods once per template. Subsequent calls will throw an error. ## Parsing existing Dockerfiles Convert existing Dockerfiles to template format using `fromDockerfile()`: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const dockerfileContent = ` FROM ubuntu:22.04 RUN apt-get update && apt-get install -y curl WORKDIR /app COPY . . ENV NODE_ENV=production ENV PORT=3000 USER appuser`; const template = Template() .fromDockerfile(dockerfileContent) .setStartCmd("npm start", waitForTimeout(5_000)); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} dockerfile_content = """ FROM ubuntu:22.04 RUN apt-get update && apt-get install -y curl WORKDIR /app COPY . . ENV NODE_ENV=production ENV PORT=3000 USER appuser """ template = ( Template() .from_dockerfile(dockerfile_content) .set_start_cmd("npm start", wait_for_timeout(5_000)) ) ``` ### Dockerfile instructions support | Instruction | Supported | Behavior | | :------------------- | :--------------------------: | :------------------------------------------------------------------------------------------------ | | `FROM` | | Sets base image | | `RUN` | | Converts to `runCmd()` / `run_cmd()` | | `COPY` / `ADD` | | Converts to `copy()` | | `WORKDIR` | | Converts to `setWorkdir()` / `set_workdir()` | | `USER` | | Converts to `setUser()` / `set_user()` | | `ENV` | | Converts to `setEnvs()` / `set_envs()`; supports both `ENV key=value` and `ENV key value` formats | | `CMD` / `ENTRYPOINT` | | Converts to `setStartCmd()` / `set_start_cmd()` with 20 seconds timeout as ready command | | `EXPOSE` | | Skipped (not supported) | | `VOLUME` | | Skipped (not supported) | Multi-stage Dockerfiles are not supported. # Build Source: https://e2b.mintlify.app/docs/template/build How to build the template ## Build and wait for completion The `build` method builds the template and waits for the build to complete. It returns build information including the template ID and build ID. ```typescript JavaScript & TypeScript wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const buildInfo = await Template.build(template, 'my-template', { cpuCount: 2, // CPU cores memoryMB: 2048, // Memory in MB skipCache: false, // Configure cache skip (except for files) onBuildLogs: defaultBuildLogger(), // Log callback receives LogEntry objects apiKey: 'your-api-key', // Override API key domain: 'your-domain', // Override domain }) // buildInfo contains: { name, templateId, buildId } ``` ```python Python wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} build_info = Template.build( template, 'my-template', cpu_count=2, # CPU cores memory_mb=2048, # Memory in MB skip_cache=False, # Configure cache skip (except for files) on_build_logs=default_build_logger(), # Log callback receives LogEntry objects api_key="your-api-key", # Override API key domain="your-domain", # Override domain ) # build_info contains: BuildInfo(name, template_id, build_id) ``` **Disk size is not a per-sandbox setting.** Unlike `cpuCount`/`cpu_count` and `memoryMB`/`memory_mb`, there is no disk-size parameter, neither on the template build nor on `Sandbox.create`. A sandbox's disk size is determined by your team's tier limit and applied when the template is built (see [Build limits](/docs/template/quickstart#build-limits)). `diskSizeMB` only ever appears in API response payloads (for example on sandbox and template info), never as an input. To get a larger disk, raise your tier or contact [support@e2b.dev](mailto:support@e2b.dev). ## Build in background The `buildInBackground` method starts the build process and returns immediately without waiting for completion. This is useful when you want to trigger a build and check its status later. ```typescript JavaScript & TypeScript wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const buildInfo = await Template.buildInBackground(template, 'my-template', { cpuCount: 2, memoryMB: 2048, }) // Returns immediately with: { name, templateId, buildId } ``` ```python Python wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} build_info = Template.build_in_background( template, 'my-template', cpu_count=2, memory_mb=2048, ) # Returns immediately with: BuildInfo(name, template_id, build_id) ``` ## Check build status Use `getBuildStatus` to check the status of a build started with `buildInBackground`. ```typescript JavaScript & TypeScript wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const status = await Template.getBuildStatus(buildInfo, { logsOffset: 0, // Optional: offset for fetching logs }) // status contains: { status: 'building' | 'ready' | 'error', logEntries: [...] } ``` ```python Python wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} status = Template.get_build_status( build_info, logs_offset=0, # Optional: offset for fetching logs ) # status contains build status and logs ``` ## Example: Background build with status polling ```typescript JavaScript & TypeScript wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Start build in background const buildInfo = await Template.buildInBackground(template, 'my-template', { cpuCount: 2, memoryMB: 2048, }) // Poll for build status let logsOffset = 0 let status = 'building' while (status === 'building') { const buildStatus = await Template.getBuildStatus(buildInfo, { logsOffset, }) logsOffset += buildStatus.logEntries.length status = buildStatus.status buildStatus.logEntries.forEach( (logEntry) => console.log(logEntry.toString()) ) // Wait for a short period before checking the status again await new Promise(resolve => setTimeout(resolve, 2000)) } if (status === 'ready') { console.log('Build completed successfully') } else { console.error('Build failed') } ``` ```python Python wrap theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Start build in background build_info = Template.build_in_background( template, 'my-template', cpu_count=2, memory_mb=2048, ) # Poll for build status import time logs_offset = 0 status = "building" while status == "building": build_status = Template.get_build_status( build_info, logs_offset=logs_offset, ) logs_offset += len(build_status.log_entries) status = build_status.status.value for log_entry in build_status.log_entries: print(log_entry) # Wait for a short period before checking the status again time.sleep(2) if status == "ready": print("Build completed successfully") else: print("Build failed") ``` # Caching Source: https://e2b.mintlify.app/docs/template/caching How the caching process works The caching concept is similar to [Docker's layer caching](https://docs.docker.com/build/cache/). For each layer command (`.copy()`, `.runCmd()`, `.setEnvs()`, etc.), we create a new layer on top of the existing. Each layer is cached based on the command and its inputs (e.g., files copied, command executed, environment variables set). If a layer command is unchanged and its inputs are the same as in any previous build, we reuse the cached layer instead of rebuilding it. This significantly speeds up the build process, especially for large templates with many layers. The cache is scoped to the team, so even if you have multiple templates, they can share the same cache if they have identical layers. ## Invalidation You can invalidate the caches only partially, or for the whole template. ### Partial Force rebuild from the next instruction, use the method: ```typescript JavaScript & TypeScript highlight={3} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const template = Template() .fromBaseImage() .skipCache() .runCmd("echo 'Hello, World!'") ``` ```python Python highlight={4} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template = ( Template() .from_base_image() .skip_cache() .run_cmd("echo 'Hello, World!'") ) ``` This will force rebuild from the next instruction, invalidating the cache for all subsequent instructions in the template. ### Whole template To force rebuild the whole template, you can use also `skipCache`/`skip_cache` parameter in the `Template.build` method: ```typescript JavaScript & TypeScript wrap highlight={2} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Template.build(template, 'my-template', { skipCache: true, // Configure cache skip (except for files) }) ``` ```python Python wrap highlight={4} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Template.build( template, 'my-template', skip_cache=True, # Configure cache skip (except for files) ) ``` This will skip the cache usage for the whole template build. ## Files caching When using the `.copy()` command, we cache the files based on their content. If the files haven't changed since the last build, we reuse them from the files cache. We differ from Docker here. Because we build the template on our infrastructure, we use improved caching on files level. Even if you invalidate the layer before `.copy()` (e.g., by changing ENV variables), we'll reuse the already uploaded files. The `copy()` command will still be re-executed, but the files for the layer will be reused from the files cache, no need to upload them from your computer again. To invalidate the cache for all subsequent instructions in the template **AND** the layer files cache, use the `forceUpload`/`force_upload` parameter. ```typescript JavaScript & TypeScript highlight={3} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const template = Template() .fromBaseImage() .copy("config.json", "/app/config.json", { forceUpload: true }) ``` ```python Python highlight={4} theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template = ( Template() .from_base_image() .copy("config.json", "/app/config.json", force_upload=True) ) ``` ## Use case for caching You can leverage caching to create templates with multiple variants (e.g., different RAM or CPU) while reusing the common layers. When building the template, just change the template name to a specific RAM/CPU configuration (e.g., `my-template-2cpu-2gb`, `my-template-1cpu-4gb`), keep the rest of the template definition the same, and the build process will reuse the cached layers. ## Optimize build times To optimize build times, place frequently changing commands (e.g., copying source code) towards the end of your template definition. This way, earlier layers can be cached and reused more often. # Defining template Source: https://e2b.mintlify.app/docs/template/defining-template How to create your own template ### Method chaining All template methods return the template instance, allowing for fluent API usage: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} const template = Template() .fromUbuntuImage("22.04") .aptInstall(["curl"]) // necessary for waitForPort .setWorkdir('/app') .copy("package.json", "/app/package.json") .runCmd("npm install") .setStartCmd("npm start", waitForPort(3000)); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template = ( Template() .from_ubuntu_image("22.04") .set_workdir("/app") .copy("package.json", "/app/package.json") .run_cmd("npm install") .set_start_cmd("npm start", wait_for_timeout(10_000))) ``` ### User and workdir Set the working directory and user for the template: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Set working directory template.setWorkdir("/app"); // Set user (runs subsequent commands as this user) template.setUser('node') template.setUser("1000:1000"); // User ID and group ID ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Set working directory template.set_workdir("/app") # Set user (runs subsequent commands as this user) template.set_user("node") template.set_user("1000:1000") # User ID and group ID ``` ### Copying files Copy files from your local filesystem to the template: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Copy a single file template.copy("package.json", "/app/package.json"); // Copy multiple files to same destination template.copy(["file1", "file2"], "/app/file") // Multiple copy operations using copyItems template.copyItems([ { src: "src/", dest: "/app/src/" }, { src: "package.json", dest: "/app/package.json" }, ]); // Copy with user and mode options template.copy("config.json", "/app/config.json", { user: "appuser", mode: 0o644, }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Copy a single file template.copy("package.json", "/app/package.json") # Copy multiple files to the same destination template.copy(["file1", "file2"], "/app/file") # Multiple copy operations using copy_items template.copy_items([ {"src": "src/", "dest": "/app/src/"}, {"src": "package.json", "dest": "/app/package.json"}, ]) # Copy with user and mode options template.copy("config.json", "/app/config.json", user="appuser", mode=0o644) ``` ### File operations Perform various file operations during template build: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Remove files or directories template.remove("/tmp/temp-file.txt"); template.remove("/old-directory", { recursive: true }); template.remove("/file.txt", { force: true }); // Rename files or directories template.rename("/old-name.txt", "/new-name.txt"); template.rename("/old-dir", "/new-dir", { force: true }); // Create directories template.makeDir("/app/logs"); template.makeDir("/app/data", { mode: 0o755 }); // Create symbolic links template.makeSymlink("/app/data", "/app/logs/data"); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Remove files or directories template.remove("/tmp/old-file") template.remove("/tmp/old-dir", recursive=True) template.remove("/tmp/file", force=True) # Force removal # Rename files or directories template.rename("/old/path", "/new/path") template.rename("/old/path", "/new/path", force=True) # Force rename # Create directories template.make_dir("/app/data") template.make_dir("/app/data", mode=0o755) # Set permissions # Create symbolic links template.make_symlink("/path/to/target", "/path/to/link") ``` ### Installing packages Install packages using package managers: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Install Python packages template.pipInstall(['requests', 'pandas', 'numpy']) // Install Python packages (user) template.pipInstall(['requests', 'pandas', 'numpy'], { g: false }) // Install Node.js packages template.npmInstall(['express', 'lodash']) // Install Node.js packages (global) template.npmInstall(['express', 'lodash'], { g: true }) // Install Bun packages template.bunInstall(['express', 'lodash']) // Install Bun packages (global) template.bunInstall(['express', 'lodash'], { g: true }) // Install system packages (automatically runs apt update) template.aptInstall(['curl', 'wget', 'git']) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Install Python packages template.pip_install(["requests", "pandas", "numpy"]) # Install Python packages (user) template.pip_install(["requests", "pandas", "numpy"], g=False) # Install Node.js packages template.npm_install(["express", "lodash"]) # Install Node.js packages (global) template.npm_install(["express", "lodash"], g=True) # Install Bun packages template.bun_install(["express", "lodash"]) # Install Bun packages (global) template.bun_install(["express", "lodash"], g=True) # Install system packages (Ubuntu/Debian) template.apt_install(["curl", "wget", "git"]) ``` ### Git operations Clone Git repositories during template build (requires `git` to be installed): ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Clone a repository template.gitClone('https://github.com/user/repo.git') // Clone a repository to a specific path template.gitClone('https://github.com/user/repo.git', '/app/repo') // Clone a specific branch template.gitClone('https://github.com/user/repo.git', '/app/repo', { branch: 'main', }) // Shallow clone with depth limit template.gitClone('https://github.com/user/repo.git', '/app/repo', { depth: 1, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Clone a repository template.git_clone("https://github.com/user/repo.git") # Clone a repository to a specific path template.git_clone("https://github.com/user/repo.git", "/app/repo") # Clone a specific branch template.git_clone("https://github.com/user/repo.git", "/app/repo", branch="main") # Shallow clone with depth limit template.git_clone("https://github.com/user/repo.git", "/app/repo", depth=1) ``` ### Environment variables Environment variables set in template definition are only available during template build. [How to setup environment variables in sandbox?](/docs/sandbox/environment-variables) Set environment variables in the template: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.setEnvs({ NODE_ENV: 'production', API_KEY: 'your-api-key', DEBUG: 'true', }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} template.set_envs({ "NODE_ENV": "production", "API_KEY": "your-api-key", "DEBUG": "true", }) ``` ### Running commands Execute shell commands during template build: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Run a single command template.runCmd('apt-get update && apt-get install -y curl') // Run multiple commands template.runCmd(['apt-get update', 'apt-get install -y curl', 'curl --version']) // Run commands as a specific user template.runCmd('npm install', { user: 'node' }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Run a single command template.run_cmd("apt-get update && apt-get install -y curl") # Run multiple commands template.run_cmd(["apt-get update", "apt-get install -y curl", "curl --version"]) # Run command as specific user template.run_cmd("npm install", user="node") ``` # Error handling Source: https://e2b.mintlify.app/docs/template/error-handling Handle errors in your template The SDK provides specific error types: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { AuthError, BuildError, FileUploadError } from 'e2b'; try { await Template.build(template, 'my-template'); } catch (error) { if (error instanceof AuthError) { console.error("Authentication failed:", error.message); } else if (error instanceof FileUploadError) { console.error("File upload failed:", error.message); } else if (error instanceof BuildError) { console.error("Build failed:", error.message); } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import AuthError, BuildError, FileUploadError try: Template.build(template, 'my-template') except AuthError as error: print(f"Authentication failed: {error}") except FileUploadError as error: print(f"File upload failed: {error}") except BuildError as error: print(f"Build failed: {error}") ``` # Claude Code Source: https://e2b.mintlify.app/docs/template/examples/claude-code Claude Code Agent available in a sandbox For a complete guide on running Claude Code in E2B sandboxes — including working with repositories, streaming output, and connecting MCP tools — see the [Agents in Sandbox: Claude Code](/docs/agents/claude-code) guide. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b' export const template = Template() .fromNodeImage('24') .aptInstall(['curl', 'git', 'ripgrep']) // Claude Code will be available globally as "claude" .npmInstall('@anthropic-ai/claude-code@latest', { g: true }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = ( Template() .from_node_image("24") .apt_install(["curl", "git", "ripgrep"]) # Claude Code will be available globally as "claude" .npm_install("@anthropic-ai/claude-code@latest", g=True) ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as claudeCodeTemplate } from './template' Template.build(claudeCodeTemplate, 'claude-code', { cpuCount: 1, memoryMB: 1024, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from .template import template as claudeCodeTemplate Template.build(claudeCodeTemplate, 'claude-code', cpu_count=1, memory_mb=1024, on_build_logs=default_build_logger(), ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // sandbox.ts import { Sandbox } from 'e2b' const sbx = await Sandbox.create('claude-code', { envs: { ANTHROPIC_API_KEY: '', }, }) console.log('Sandbox created', sbx.sandboxId) // Print help for Claude Code // const result = await sbx.commands.run('claude --help') // console.log(result.stdout) // Run a prompt with Claude Code const result = await sbx.commands.run( `claude --dangerously-skip-permissions -p 'Create a hello world index.html'`, { timeoutMs: 0 } ) console.log(result.stdout) sbx.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # sandbox.py from e2b import Sandbox sbx = Sandbox( 'claude-code', envs={ 'ANTHROPIC_API_KEY': '', }, ) print("Sandbox created", sbx.sandbox_id) # Print help for Claude Code # result = sbx.commands.run('claude --help') # print(result.stdout) # Run a prompt with Claude Code result = sbx.commands.run( "claude --dangerously-skip-permissions -p 'Create a hello world index.html'", timeout=0, ) print(result.stdout) sbx.kill() ``` # Desktop Source: https://e2b.mintlify.app/docs/template/examples/desktop Sandbox with Ubuntu Desktop and VNC access This template creates a sandbox with a full Ubuntu 22.04 desktop environment, including the XFCE desktop, common applications, and VNC streaming for remote access. It's ideal for building AI agents that need to interact with graphical user interfaces. The template includes: * **Ubuntu 22.04** with XFCE desktop environment * **VNC streaming** via [noVNC](https://novnc.com/) for browser-based access * **Pre-installed applications**: LibreOffice, text editors, file manager, and common utilities * **Automation tools**: [xdotool](https://github.com/jordansissel/xdotool) and [scrot](https://github.com/resurrecting-open-source-projects/scrot) for programmatic desktop control ## Template definition The template installs the desktop environment, sets up VNC streaming via [x11vnc](https://github.com/LibVNC/x11vnc) and noVNC, and configures a startup script. ```typescript JavaScript & TypeScript expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template, waitForPort } from 'e2b' export const template = Template() .fromUbuntuImage('22.04') // Desktop environment and system utilities .runCmd([ 'yes | unminimize', 'apt-get update', 'apt-get install -y \ xserver-xorg \ xorg \ x11-xserver-utils \ xvfb \ x11-utils \ xauth \ xfce4 \ xfce4-goodies \ util-linux \ sudo \ curl \ git \ wget \ python3-pip \ xdotool \ scrot \ ffmpeg \ x11vnc \ net-tools \ netcat \ x11-apps \ libreoffice \ xpdf \ gedit \ xpaint \ tint2 \ galculator \ pcmanfm', 'apt-get clean', 'rm -rf /var/lib/apt/lists/*', ]) // Streaming server setup .runCmd([ 'git clone --branch e2b-desktop https://github.com/e2b-dev/noVNC.git /opt/noVNC', 'ln -s /opt/noVNC/vnc.html /opt/noVNC/index.html', 'git clone --branch v0.12.0 https://github.com/novnc/websockify /opt/noVNC/utils/websockify', ]) // Set default terminal .runCmd( 'ln -sf /usr/bin/xfce4-terminal.wrapper /etc/alternatives/x-terminal-emulator' ) .copy('start_command.sh', '/start_command.sh') .runCmd('chmod +x /start_command.sh') .setStartCmd('/start_command.sh', waitForPort(6080)) ``` ```python Python expandable theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template, wait_for_port template = ( Template() .from_image("ubuntu:22.04") # Initial system setup and packages # We are not using .apt_install() here because some packages have interactive prompts (keyboard layout setup, etc.) .run_cmd( [ "yes | unminimize", "apt-get update", "apt-get install -y \ xserver-xorg \ xorg \ x11-xserver-utils \ xvfb \ x11-utils \ xauth \ xfce4 \ xfce4-goodies \ util-linux \ sudo \ curl \ git \ wget \ python3-pip \ xdotool \ scrot \ ffmpeg \ x11vnc \ net-tools \ netcat \ x11-apps \ libreoffice \ xpdf \ gedit \ xpaint \ tint2 \ galculator \ pcmanfm", "apt-get clean", "rm -rf /var/lib/apt/lists/*", ] ) # Setup NoVNC and websockify .run_cmd( [ "git clone --branch e2b-desktop https://github.com/e2b-dev/noVNC.git /opt/noVNC", "ln -s /opt/noVNC/vnc.html /opt/noVNC/index.html", "git clone --branch v0.12.0 https://github.com/novnc/websockify /opt/noVNC/utils/websockify", ] ) # Set default terminal .run_cmd( "ln -sf /usr/bin/xfce4-terminal.wrapper /etc/alternatives/x-terminal-emulator" ) # Copy the start command .copy("start_command.sh", "/start_command.sh") .run_cmd("chmod +x /start_command.sh") # Set start command to launch the desktop environment .set_start_cmd("/start_command.sh", wait_for_port(6080)) ) ``` ## Startup script The startup script initializes the virtual display using [Xvfb](https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml) (X Virtual Framebuffer), launches the XFCE desktop session, starts the VNC server, and exposes the desktop via noVNC on port 6080. This script runs automatically when the sandbox starts. ```bash start_command.sh theme={"theme":{"light":"github-light","dark":"github-dark-default"}} #!/bin/bash # Set display export DISPLAY=${DISPLAY:-:0} # Start Xvfb Xvfb $DISPLAY -ac -screen 0 1024x768x24 -nolisten tcp & sleep 2 # Start XFCE session startxfce4 & sleep 5 # Start VNC server x11vnc -bg -display $DISPLAY -forever -wait 50 -shared -rfbport 5900 -nopw \ -noxdamage -noxfixes -nowf -noscr -ping 1 -repeat -speeds lan & sleep 2 # Start noVNC server cd /opt/noVNC/utils && ./novnc_proxy --vnc localhost:5900 --listen 6080 --web /opt/noVNC --heartbeat 30 & sleep 2 ``` ## Building the template Build the template with increased CPU and memory allocation to handle the desktop environment installation. The build process may take several minutes due to the size of the packages being installed. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as desktopTemplate } from './template' await Template.build(desktopTemplate, 'desktop', { cpuCount: 8, memoryMB: 8192, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from .template import template as desktopTemplate Template.build(desktopTemplate, 'desktop', cpu_count=8, memory_mb=8192, on_build_logs=default_build_logger(), ) ``` # Docker Source: https://e2b.mintlify.app/docs/template/examples/docker Sandbox with Docker or Docker Compose installed for running containers ## Docker ### Template Use the official installation script from [get.docker.com](https://get.docker.com). The `hello-world` container run validates the installation. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template } from 'e2b' export const template = Template() .fromUbuntuImage('24.04') .runCmd('curl -fsSL https://get.docker.com | sudo sh') .runCmd('sudo docker run --rm hello-world') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template template = ( Template() .from_ubuntu_image("24.04") .run_cmd("curl -fsSL https://get.docker.com | sudo sh") .run_cmd("sudo docker run --rm hello-world") ) ``` ### Build We recommend at least 2 CPUs and 2 GB of RAM for running Docker containers. **With lower RAM, your sandbox might run out of memory.** ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as dockerTemplate } from './template' Template.build(dockerTemplate, 'docker', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from .template import template as dockerTemplate Template.build(dockerTemplate, 'docker', cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` ### Run Run an Alpine container that prints a hello message. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // sandbox.ts import { Sandbox } from 'e2b' const sbx = await Sandbox.create('docker') const result = await sbx.commands.run('sudo docker run --rm alpine echo "Hello from Alpine!"') console.log(result.stdout) await sbx.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # sandbox.py from e2b import Sandbox sbx = Sandbox.create('docker') result = sbx.commands.run('sudo docker run --rm alpine echo "Hello from Alpine!"') print(result.stdout) sbx.kill() ``` ## Docker Compose This example installs Docker and Docker Compose, then validates the setup with a Compose version check and a sample Compose run. ### Template Create a new file named `template-compose.ts` (or `template_compose.py`). ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template-compose.ts import { Template } from 'e2b' export const composeTemplate = Template() .fromUbuntuImage('24.04') .runCmd([ 'set -euxo pipefail', 'sudo apt-get update', 'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io', 'sudo usermod -aG docker user', 'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin || true', 'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-v2 || true', 'sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose || true', 'sudo docker compose version || sudo docker-compose --version', ]) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template_compose.py from e2b import Template compose_template = ( Template() .from_ubuntu_image("24.04") .run_cmd( [ "set -euxo pipefail", "sudo apt-get update", "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io", "sudo usermod -aG docker user", "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin || true", "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-v2 || true", "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose || true", "sudo docker compose version || sudo docker-compose --version", ] ) ) ``` Expected result: you now have a local `template-compose.ts` or `template_compose.py` file. ### Build ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build-compose.ts import { Template, defaultBuildLogger } from 'e2b' import { composeTemplate } from './template-compose' Template.build(composeTemplate, 'docker-compose', { cpuCount: 2, memoryMB: 2048, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build_compose.py from e2b import Template, default_build_logger from template_compose import compose_template Template.build(compose_template, "docker-compose", cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) ``` Expected output (example): ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} BuildInfo(... name='docker-compose', alias='docker-compose', tags=['default']) ``` ### Run ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // sandbox-compose.ts import { Sandbox } from 'e2b' const sbx = await Sandbox.create('docker-compose') await sbx.commands.run('mkdir -p /tmp/docker-compose-test') await sbx.files.write('/tmp/docker-compose-test/compose.yaml', [ 'services:', ' hello:', ' image: busybox:1.36', ' command: ["sh", "-lc", "echo docker-compose-ok"]', '', ].join('\n')) const result = await sbx.commands.run(` set -euxo pipefail cd /tmp/docker-compose-test if docker compose version >/dev/null 2>&1; then docker compose up --abort-on-container-exit --remove-orphans docker compose down --remove-orphans -v echo "Docker Compose ran successfully" elif docker-compose --version >/dev/null 2>&1; then docker-compose up --abort-on-container-exit --remove-orphans docker-compose down --remove-orphans -v echo "Docker Compose ran successfully" else echo "No compose command available" exit 127 fi `) console.log(result.stdout) await sbx.kill() ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # sandbox_compose.py from e2b import Sandbox sbx = Sandbox.create("docker-compose") sbx.commands.run("mkdir -p /tmp/docker-compose-test") sbx.files.write( "/tmp/docker-compose-test/compose.yaml", """ services: hello: image: busybox:1.36 command: ["sh", "-lc", "echo docker-compose-ok"] """, ) result = sbx.commands.run( """ set -euxo pipefail cd /tmp/docker-compose-test if docker compose version >/dev/null 2>&1; then docker compose up --abort-on-container-exit --remove-orphans docker compose down --remove-orphans -v echo "Docker Compose ran successfully" elif docker-compose --version >/dev/null 2>&1; then docker-compose up --abort-on-container-exit --remove-orphans docker-compose down --remove-orphans -v echo "Docker Compose ran successfully" else echo "No compose command available" exit 127 fi """, ) print(result.stdout) sbx.kill() ``` Expected output (example): ```text theme={"theme":{"light":"github-light","dark":"github-dark-default"}} hello_1 | docker-compose-ok Docker Compose ran successfully ``` # Expo app Source: https://e2b.mintlify.app/docs/template/examples/expo Expo web app running in the sandbox using Node.js Basic Expo app. The development server runs on port 8081 as soon as the sandbox is ready. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { defaultBuildLogger, waitForURL } from "e2b"; export const template = Template() .fromNodeImage() .setWorkdir("/home/user/expo-app") .runCmd("npx create-expo-app@latest . --yes") .runCmd("mv /home/user/expo-app/* /home/user/ && rm -rf /home/user/expo-app") .setWorkdir("/home/user") .setStartCmd("npx expo start", waitForURL("http://localhost:8081")); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template, wait_for_url template = ( Template() .from_node_image() .set_workdir("/home/user/expo-app") .run_cmd("npx create-expo-app@latest . --yes") .run_cmd("mv /home/user/expo-app/* /home/user/ && rm -rf /home/user/expo-app") .set_workdir("/home/user") .set_start_cmd("npx expo start", wait_for_url('http://localhost:8081')) ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as expoTemplate } from './template' Template.build(expoTemplate, 'expo-app', { cpuCount: 4, memoryMB: 8192, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from .template import template as expoTemplate Template.build(expoTemplate, 'expo-app', cpu_count=4, memory_mb=8192, on_build_logs=default_build_logger(), ) ``` # Next.js app Source: https://e2b.mintlify.app/docs/template/examples/nextjs Next.js web app running in the sandbox using Node.js Basic Next.js app with Tailwind and shadcn UI The development server runs on port 3000 as soon as the sandbox is ready. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template, waitForURL } from 'e2b' export const template = Template() .fromNodeImage('21-slim') .setWorkdir('/home/user/nextjs-app') .runCmd( 'npx create-next-app@14.2.30 . --ts --tailwind --no-eslint --import-alias "@/*" --use-npm --no-app --no-src-dir' ) .runCmd('npx shadcn@2.1.7 init -d') .runCmd('npx shadcn@2.1.7 add --all') .runCmd( 'mv /home/user/nextjs-app/* /home/user/ && rm -rf /home/user/nextjs-app' ) .setWorkdir('/home/user') .setStartCmd('npx next --turbo', waitForURL('http://localhost:3000')) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template, wait_for_url template = ( Template() .from_node_image("21-slim") .set_workdir("/home/user/nextjs-app") .run_cmd( 'npx create-next-app@14.2.30 . --ts --tailwind --no-eslint --import-alias "@/*" --use-npm --no-app --no-src-dir' ) .run_cmd("npx shadcn@2.1.7 init -d") .run_cmd("npx shadcn@2.1.7 add --all") .run_cmd("mv /home/user/nextjs-app/* /home/user/ && rm -rf /home/user/nextjs-app") .set_workdir("/home/user") .set_start_cmd("npx next --turbo", wait_for_url('http://localhost:3000')) ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as nextJSTemplate } from './template' Template.build(nextJSTemplate, 'nextjs-app', { cpuCount: 4, memoryMB: 4096, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from .template import template as nextjsTemplate Template.build(nextjsTemplate, 'nextjs-app', cpu_count=4, memory_mb=4096, on_build_logs=default_build_logger(), ) ``` # Next.js app (Bun) Source: https://e2b.mintlify.app/docs/template/examples/nextjs-bun Next.js web app running in the sandbox using Bun Basic Next.js app with Tailwind and shadcn UI using Bun. The development server runs on port 3000 as soon as the sandbox is ready. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template, waitForURL } from 'e2b' export const template = Template() .fromBunImage('1.3') .setWorkdir('/home/user/nextjs-app') .runCmd( 'bun create next-app --app --ts --tailwind --turbopack --yes --use-bun .' ) .runCmd('bunx --bun shadcn@latest init -d') .runCmd('bunx --bun shadcn@latest add --all') .runCmd( 'mv /home/user/nextjs-app/* /home/user/ && rm -rf /home/user/nextjs-app' ) .setWorkdir('/home/user') .setStartCmd('bun --bun run dev --turbo', waitForURL('http://localhost:3000')) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template, wait_for_url template = ( Template() .from_bun_image('1.3') .set_workdir('/home/user/nextjs-app') .run_cmd('bun create next-app --app --ts --tailwind --turbopack --yes --use-bun .') .run_cmd('bunx --bun shadcn@latest init -d') .run_cmd('bunx --bun shadcn@latest add --all') .run_cmd('mv /home/user/nextjs-app/* /home/user/ && rm -rf /home/user/nextjs-app') .set_workdir('/home/user') .set_start_cmd('bun --bun run dev --turbo', wait_for_url('http://localhost:3000')) ) ``` ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.ts import { Template, defaultBuildLogger } from 'e2b' import { template as nextJSTemplate } from './template' Template.build(nextJSTemplate, 'nextjs-app-bun', { cpuCount: 4, memoryMB: 4096, onBuildLogs: defaultBuildLogger(), }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build.py from e2b import Template, default_build_logger from .template import template as nextjsTemplate Template.build(nextjsTemplate, 'nextjs-app-bun', cpu_count=4, memory_mb=4096, on_build_logs=default_build_logger(), ) ``` # How it works Source: https://e2b.mintlify.app/docs/template/how-it-works How the template building process works ## General overview Every time you are building a sandbox template, we create a container based on the definition. We extract the container's filesystem, do provisioning and configuration (e.g. installing required dependencies), run layers commands and start a sandbox. Then, these steps happen: 1. We take the running sandbox. 2. (Only if you specified the [start command](/docs/template/start-ready-command#start-command), otherwise this step is skipped) Execute the start command. 3. Wait for readiness (by default 20 seconds if start command is specified, otherwise immediately ready). Readiness check can be configured using [ready command](/docs/template/start-ready-command#ready-command). 4. Snapshot the sandbox and make it ready for you to spawn it with the SDK. We call this sandbox snapshot a *sandbox template*. Snapshots are saved running sandboxes. We serialize and save the whole sandbox's filesystem together with all the running processes in a way that can be loaded later. This allows us to load the sandbox in \~80ms any time later with all the processes already running and the filesystem exactly as it was. ## Default user and workdir To learn more about default user and workdir, please refer to the [User and workdir](/docs/template/user-and-workdir) section. ## Caching To learn more about caching, please refer to the [Caching](/docs/template/caching) section. ## Kernel E2B sandboxes run on an **LTS 6.1 Linux kernel**.\ The exact kernel version available in your sandbox depends on **when your template was built**: | Template build date (DD.MM.YYYY) | Kernel version | | -------------------------------- | ----------------- | | ≥ 27.11.2025 | 6.1.158 (current) | | \< 27.11.2025 | 6.1.102 | > **Note:**\ > Kernel versions are fixed at **template build time**.\ > If your template was created on an older kernel, **the kernel cannot be upgraded or changed** for that template.\ > To use a newer kernel, you must **rebuild the template** or create a new one. You can find the kernel configuration files for each version [here](https://github.com/e2b-dev/fc-kernels/tree/main/configs). # Logging Source: https://e2b.mintlify.app/docs/template/logging How to view logs from the template build You can retrieve the build logs using the SDK. ## Default logger We provide a default logger that you can use to filter logs by level: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, defaultBuildLogger } from 'e2b'; await Template.build(template, 'my-template', { onBuildLogs: defaultBuildLogger({ minLevel: "info", // Minimum log level to show }), }); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, default_build_logger Template.build( template, 'my-template', on_build_logs=default_build_logger( min_level="info", # Minimum log level to show ) ) ``` ## Custom logger You can customize how logs are handled: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Simple logging onBuildLogs: (logEntry) => console.log(logEntry.toString()); // Custom formatting onBuildLogs: (logEntry) => { const time = logEntry.timestamp.toISOString(); console.log(`[${time}] ${logEntry.level.toUpperCase()}: ${logEntry.message}`); }; // Filter by log level onBuildLogs: (logEntry) => { if (logEntry.level === "error" || logEntry.level === "warn") { console.error(logEntry.toString()); } }; ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Simple logging on_build_logs=lambda log_entry: print(log_entry) # Custom formatting def custom_logger(log_entry): time = log_entry.timestamp.isoformat() print(f"[{time}] {log_entry.level.upper()}: {log_entry.message}") Template.build(template, 'my-template', on_build_logs=custom_logger) # Filter by log level def error_logger(log_entry): if log_entry.level in ["error", "warn"]: print(f"ERROR/WARNING: {log_entry}", file=sys.stderr) Template.build(template, 'my-template', on_build_logs=error_logger) ``` The `onBuildLogs`/`on_build_logs` callback receives structured `LogEntry` objects with the following properties: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} type LogEntryLevel = 'debug' | 'info' | 'warn' | 'error' class LogEntry { constructor( public readonly timestamp: Date, public readonly level: LogEntryLevel, public readonly message: string ) toString() // Returns formatted log string } // Indicates the start of the build process class LogEntryStart extends LogEntry { constructor(timestamp: Date, message: string) { super(timestamp, 'debug', message) } } // Indicates the end of the build process class LogEntryEnd extends LogEntry { constructor(timestamp: Date, message: string) { super(timestamp, 'debug', message) } } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} LogEntryLevel = Literal["debug", "info", "warn", "error"] @dataclass class LogEntry: timestamp: datetime level: LogEntryLevel message: str def __str__(self) -> str: # Returns formatted log string # Indicates the start of the build process @dataclass class LogEntryStart(LogEntry): level: LogEntryLevel = field(default="debug", init=False) # Indicates the end of the build process @dataclass class LogEntryEnd(LogEntry): level: LogEntryLevel = field(default="debug", init=False) ``` Together with the `LogEntry` type, there are also `LogEntryStart` and `LogEntryEnd` types that indicate the start and end of the build process. Their default log level is `debug` and you can use them to like this: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} if (logEntry instanceof LogEntryStart) { // Build started return } if (logEntry instanceof LogEntryEnd) { // Build ended return } ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} if isinstance(log_entry, LogEntryStart): # Build started return if isinstance(log_entry, LogEntryEnd): # Build ended return ``` # Template names Source: https://e2b.mintlify.app/docs/template/names Understanding and managing template names Template names are unique identifiers used to reference and create sandboxes from your templates. They serve as human-readable names that make it easy to identify and use your templates across your applications. ## What is a template name? A name is a string identifier that you assign to a template when building it. Once a template is built with a name, you can use that name to create sandboxes from the template. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Build a template with a name await Template.build(template, 'my-python-env', { cpuCount: 2, memoryMB: 2048, }) // Create a sandbox using the name const sandbox = await Sandbox.create('my-python-env') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Build a template with a name Template.build( template, 'my-python-env', cpu_count=2, memory_mb=2048, ) # Create a sandbox using the name sandbox = Sandbox.create('my-python-env') ``` ## Name format Before a name is used, it's trimmed of surrounding whitespace and lowercased. The result must match the pattern `^[a-z0-9-_]+$`: * Lowercase letters (`a`–`z`), numbers (`0`–`9`), dashes (`-`), and underscores (`_`) * Between 1 and 128 characters * Leading and trailing dashes or underscores are allowed Uppercase letters are accepted on input and lowercased automatically, so `My-Template` and `my-template` refer to the same name. Any other character (spaces inside the name, dots, slashes, and so on) is rejected. ## Team-local naming Template names are scoped to your team. This means: * Your template named `my-app` is stored as `your-team-slug/my-app` * You can reference it simply as `my-app` within your team * Other teams can have their own `my-app` template without conflict * Public templates should be referenced using the full namespaced format (`team-slug/template-name`) **Backwards Compatibility**: Existing public templates remain accessible without the team slug prefix. New public templates should be referenced using the full namespaced format (`team-slug/template-name`). ## Common use cases ### Development and production environments Use different names for different environments: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Development template await Template.build(template, 'myapp-dev', { cpuCount: 1, memoryMB: 1024, }) // Production template await Template.build(template, 'myapp-prod', { cpuCount: 4, memoryMB: 4096, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Development template Template.build( template, 'myapp-dev', cpu_count=1, memory_mb=1024, ) # Production template Template.build( template, 'myapp-prod', cpu_count=4, memory_mb=4096, ) ``` ### Multiple template variants Create different variants of the same template with different configurations: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Small instance await Template.build(template, 'myapp-small', { cpuCount: 1, memoryMB: 512, }) // Large instance await Template.build(template, 'myapp-large', { cpuCount: 8, memoryMB: 8192, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Small instance Template.build( template, 'myapp-small', cpu_count=1, memory_mb=512, ) # Large instance Template.build( template, 'myapp-large', cpu_count=8, memory_mb=8192, ) ``` When building variants with the same template definition but different CPU/RAM configurations, E2B's caching system will reuse common layers, making subsequent builds much faster. ## Checking name availability You can check if a name is already in use within your team with the `exists` method. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template } from 'e2b' const exists = await Template.exists('my-template') console.log(`Name ${exists ? 'is taken' : 'is available'}`) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template exists = Template.exists('my-template') print(f"Name {'is taken' if exists else 'is available'}") ``` ## Best practices 1. **Use descriptive names**: Choose names that clearly indicate the template's purpose or configuration 2. **Use tags for versioning**: Instead of baking version numbers into names, use [tags](/docs/template/tags) for version management (e.g., `myapp:v1`, `myapp:v2`) 3. **Use consistent naming**: Establish a naming convention for your team and stick to it # Private registries Source: https://e2b.mintlify.app/docs/template/private-registries Access private registries as the base image If your base image is hosted in a private registry, you can provide credentials using the following helpers: * General registry * GCP Artifact Registry * AWS ECR ## General registry ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Template().fromImage('ubuntu:22.04', { username: 'user', password: 'pass', }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Template().from_image( image="ubuntu:22.04", username="user", password="pass", ) ``` ## GCP Artifact Registry ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // From file path Template().fromGCPRegistry('ubuntu:22.04', { serviceAccountJSON: './service_account.json', }) // From object Template().fromGCPRegistry('ubuntu:22.04', { serviceAccountJSON: { project_id: '123', private_key_id: '456' }, }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # From file path Template().from_gcp_registry( image="ubuntu:22.04", service_account_json="./service_account.json", ) # From object Template().from_gcp_registry( image="ubuntu:22.04", service_account_json={"project_id": "123", "private_key_id": "456"}, ) ``` ## AWS ECR ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Template().fromAWSRegistry('ubuntu:22.04', { accessKeyId: '123', secretAccessKey: '456', region: 'us-west-1', }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} Template().from_aws_registry( image="ubuntu:22.04", access_key_id="123", secret_access_key="456", region="us-west-1", ) ``` # Quickstart Source: https://e2b.mintlify.app/docs/template/quickstart Start with custom sandbox templates E2B templates allow you to define custom sandboxes. You can define the base image, environment variables, files to copy, commands to run, and a [start command](/docs/template/start-ready-command#start-command) that runs during the template build and is captured in a snapshot — so the process is **already running** when you create a sandbox from that template. This gives you fully configured sandboxes with running processes ready to use with zero wait time for your users. There are two ways how you can start creating a new template: * using the CLI * manually using the SDK ## CLI You can use the E2B CLI to create a new template. Install the latest version of the [E2B CLI](https://e2b.dev/docs/cli) ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} e2b template init ``` Follow the prompts to create a new template. Check the generated `README.md` file to see how to build and use your new template. ## Manual ### Install the packages Requires the E2B SDK version at least 2.3.0 ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npm install e2b dotenv ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} pip install e2b dotenv ``` Create the `.env` file ```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}} E2B_API_KEY=e2b_*** ``` ### Create a new template file Create a template file with the following name and content ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // template.ts import { Template, waitForTimeout } from 'e2b'; export const template = Template() .fromBaseImage() .setEnvs({ HELLO: "Hello, World!", }) .setStartCmd("echo $HELLO", waitForTimeout(5_000)); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # template.py from e2b import Template, wait_for_timeout template = ( Template() .from_base_image() .set_envs( { "HELLO": "Hello, World!", } ) .set_start_cmd("echo $HELLO", wait_for_timeout(5_000))) ``` ### Create a development build script ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.dev.ts import 'dotenv/config'; import { Template, defaultBuildLogger } from 'e2b'; import { template } from './template'; async function main() { await Template.build(template, 'template-tag-dev', { cpuCount: 1, memoryMB: 1024, onBuildLogs: defaultBuildLogger(), }); } main().catch(console.error); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build_dev.py from dotenv import load_dotenv from e2b import Template, default_build_logger from template import template load_dotenv() if __name__ == '__main__': Template.build( template, 'template-tag-dev', cpu_count=1, memory_mb=1024, on_build_logs=default_build_logger(), ) ``` ### Create a production build script ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // build.prod.ts import 'dotenv/config'; import { Template, defaultBuildLogger } from 'e2b'; import { template } from './template'; async function main() { await Template.build(template, 'template-tag', { cpuCount: 1, memoryMB: 1024, onBuildLogs: defaultBuildLogger(), }); } main().catch(console.error); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # build_prod.py from dotenv import load_dotenv from e2b import Template, default_build_logger from template import template load_dotenv() if __name__ == '__main__': Template.build( template, 'template-tag', cpu_count=1, memory_mb=1024, on_build_logs=default_build_logger(), ) ``` ### Build the template Build the development template ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.dev.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build_dev.py ``` Build the production template ```bash JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} npx tsx build.prod.ts ``` ```bash Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} python build_prod.py ``` ## Create a new Sandbox from the Template ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import 'dotenv/config'; import { Sandbox } from 'e2b'; // Create a Sandbox from development template const sandbox = await Sandbox.create("template-tag-dev"); // Create a Sandbox from production template const sandbox = await Sandbox.create("template-tag"); ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from dotenv import load_dotenv from e2b import Sandbox load_dotenv() # Create a new Sandbox from the development template sbx = Sandbox(template="template-tag-dev") # Create a new Sandbox from the production template sbx = Sandbox(template="template-tag") ``` The template name is the identifier that can be used to create a new Sandbox. ## Scaling templates You can create a large number of templates with low overhead, for example one template per customer, per project, or per agent run. There's currently no limit on the number of templates, though pricing for total storage used by templates may be introduced in the future. The template build environment is a full sandbox environment, so you can do anything during the build that you can do inside a running sandbox, including running Docker containers as part of the setup. Compared to [snapshots](/docs/sandbox/snapshots), templates start faster and use fewer resources because the guest OS is restarted before the long-running process is captured and prefetching is significantly more effective. If your workload involves spinning up many per-customer or per-project environments, prefer templates over snapshots. ### Layering templates with `fromTemplate` When you build many similar templates (e.g. a customer-specific template per customer that all share the same base setup), use [`fromTemplate`](/docs/sdk-reference/js-sdk/latest/template#fromtemplate) to start from an existing template instead of rebuilding the shared layers from scratch each time. This keeps per-customer builds fast and reuses the cached base. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template } from 'e2b' // Per-customer template built on top of a shared base template export const template = Template() .fromTemplate('my-base-template') .copy('./customers/acme', '/app/config') .setEnvs({ CUSTOMER_ID: 'acme' }) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template # Per-customer template built on top of a shared base template template = ( Template() .from_template("my-base-template") .copy("./customers/acme", "/app/config") .set_envs({"CUSTOMER_ID": "acme"}) ) ``` ## Build limits Template builds are subject to the following limits: * **Max build duration**: Builds can run for **up to 1 hour**. If a build exceeds this, it will be terminated. * **Max vCPUs per build**: 8 on Hobby, 8+ on Pro, custom on Enterprise. * **Max memory per build**: 8 GB on Hobby, 8+ GB on Pro, custom on Enterprise. * **Max disk size per build**: 10 GB on Hobby, 20+ GB on Pro, custom on Enterprise. * **Concurrent builds**: 20 on Hobby and Pro, custom on Enterprise. See [Billing & limits](/docs/billing#plans) for the full list of plan limits. Need higher limits? Contact [support@e2b.dev](mailto:support@e2b.dev). # Start & ready commands Source: https://e2b.mintlify.app/docs/template/start-ready-command Define running processes for the sandbox ## Start command The start command specifies a process that runs at the **end of the template build** — not when a sandbox is created. During the build, E2B executes the start command, waits for the [ready command](#ready-command) to confirm the process is up, and then takes a [snapshot](/docs/template/how-it-works) of the entire sandbox including the running process. When you later create a sandbox from that template, the snapshotted process is **already running** — there is no startup wait. This is how you get servers, seeded databases, or any long-running process available instantly when spawning sandboxes with the SDK. The start command runs **once during template build** and is captured in a snapshot. It does not re-execute each time you create a sandbox. If you need to run a command every time a sandbox is created, use `sandbox.commands.run()` after creating the sandbox instead. This also means that [environment variables passed to `Sandbox.create()`](/docs/sandbox/environment-variables#1-global-environment-variables) are **not available** to the start command process — it already ran during the build. If your start command needs environment variables, set them in the template definition using `setEnvs()` / `set_envs()`. You can see the full build process [here](/docs/template/how-it-works). ## Ready command The ready command determines when the sandbox is ready before a [snapshot](/docs/template/how-it-works) is created. It is executed in an infinite loop until it returns a successful **exit code 0**. This lets you control how long the build waits for the [start command](#start-command) or any other system state to be ready. ## `setStartCmd` / `set_start_cmd` Use `setStartCmd` / `set_start_cmd` when you want to run a process during the template build **and** wait for it to be ready. This method accepts **two arguments**: the start command and the ready command. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, waitForPort, waitForURL, waitForTimeout } from 'e2b' // Start a Python HTTP server and wait for it to listen on port 8000 const template = Template() .fromUbuntuImage("22.04") .aptInstall(["curl", "python3"]) .setStartCmd('python3 -m http.server 8000', waitForPort(8000)) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, wait_for_port, wait_for_url, wait_for_timeout # Start a Python HTTP server and wait for it to listen on port 8000 template = ( Template() .from_ubuntu_image("22.04") .apt_install(["curl", "python3"]) .set_start_cmd("python3 -m http.server 8000", wait_for_port(8000)) ) ``` You can also pass a custom shell command as the ready command instead of using a helper: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Start command with a custom ready command template.setStartCmd('npm start', 'curl -s http://localhost:3000/health') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Start command with a custom ready command template.set_start_cmd("npm start", "curl -s http://localhost:3000/health") ``` More examples: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, waitForURL, waitForPort } from 'e2b' // Next.js app — wait for the dev server URL template.setStartCmd('npx next --turbo', waitForURL('http://localhost:3000')) // Python HTTP server — wait for port 8000 template.setStartCmd('python -m http.server 8000', waitForPort(8000)) // VNC desktop — wait for the VNC port template.setStartCmd('/start_command.sh', waitForPort(6080)) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, wait_for_url, wait_for_port # Next.js app — wait for the dev server URL template.set_start_cmd("npx next --turbo", wait_for_url("http://localhost:3000")) # Python HTTP server — wait for port 8000 template.set_start_cmd("python -m http.server 8000", wait_for_port(8000)) # VNC desktop — wait for the VNC port template.set_start_cmd("/start_command.sh", wait_for_port(6080)) ``` ## `setReadyCmd` / `set_ready_cmd` Use `setReadyCmd` / `set_ready_cmd` when you **don't need a start command** but still want to control when the sandbox snapshot is taken. This method accepts only **one argument**: the ready command. This is useful when your template's build steps (e.g., `runCmd` / `run_cmd`) already start a background process or when you just need extra time for the system to settle before snapshotting. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { Template, waitForTimeout, waitForPort, waitForFile } from 'e2b' // Wait 10 seconds before taking the snapshot const template = Template() .fromUbuntuImage("22.04") .runCmd("apt-get install -y nginx && service nginx start") .setReadyCmd(waitForPort(80)) ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import Template, wait_for_timeout, wait_for_port, wait_for_file # Wait for nginx to start listening before taking the snapshot template = ( Template() .from_ubuntu_image("22.04") .run_cmd("apt-get install -y nginx && service nginx start") .set_ready_cmd(wait_for_port(80)) ) ``` More examples: ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // Wait for a file to be created by a background process template.setReadyCmd(waitForFile('/tmp/ready')) // Wait a fixed duration for the system to stabilize template.setReadyCmd(waitForTimeout(10_000)) // Custom readiness check template.setReadyCmd('curl -s http://localhost:8080/health') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # Wait for a file to be created by a background process template.set_ready_cmd(wait_for_file("/tmp/ready")) # Wait a fixed duration for the system to stabilize template.set_ready_cmd(wait_for_timeout(10_000)) # Custom readiness check template.set_ready_cmd("curl -s http://localhost:8080/health") ``` ## Ready command helpers The SDK provides helper functions for common ready command patterns. These can be used with both `setStartCmd` / `set_start_cmd` and `setReadyCmd` / `set_ready_cmd`. ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} import { waitForPort, waitForURL, waitForProcess, waitForFile, waitForTimeout, } from 'e2b' // Wait for a port to be available waitForPort(3000) // Wait for a URL to return a specific status code (defaults to 200) waitForURL('http://localhost:3000/health') waitForURL('http://localhost:3000/health', 200) // Wait for a process to be running waitForProcess('node') // Wait for a file to exist waitForFile('/tmp/ready') // Wait for a specified duration waitForTimeout(10_000) // 10 seconds ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} from e2b import wait_for_port, wait_for_url, wait_for_process, wait_for_file, wait_for_timeout # Wait for a port to be available wait_for_port(3000) # Wait for a URL to return a specific status code (defaults to 200) wait_for_url("http://localhost:3000/health") wait_for_url("http://localhost:3000/health", 200) # Wait for a process to be running wait_for_process("node") # Wait for a file to exist wait_for_file("/tmp/ready") # Wait for a specified duration wait_for_timeout(10_000) # 10 seconds ``` # Template tags & versioning Source: https://e2b.mintlify.app/docs/template/tags Use tags to version your templates and manage environment-based deployments Template versioning allows you to maintain multiple versions of the same template using tags. This enables workflows like semantic versioning, environment-based deployments, and gradual rollouts. ## Tag format Tags follow the `name:tag` format, where `name` is your template's identifier and `tag` is the version label. ``` my-template:v1.0.0 // Within your team my-template:production // Within your team acme/my-template:v1.0.0 // Full namespaced reference ``` ## The default tag When you build or reference a template without specifying a tag, E2B uses the `default` tag automatically. This means: * `my-template` is equivalent to `my-template:default` * Existing templates without tags continue to work seamlessly ```typescript JavaScript & TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-default"}} // These are equivalent const sandbox1 = await Sandbox.create('my-template') const sandbox2 = await Sandbox.create('my-template:default') ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-default"}} # These are equivalent sandbox1 = Sandbox.create('my-template') sandbox2 = Sandbox.create('my-template:default') ``` ## Referencing a specific build Instead of using a named tag, you can start a sandbox from a specific build by passing its `build_id` directly. This is useful when you need to pin a sandbox to an exact build artifact — for example, during debugging or when reproducing an issue from a known build. The format follows the same colon syntax as tags: `