Skip to main content

E2B

Executes commands in isolated E2B cloud sandboxes. Provides secure, ephemeral environments with support for mounting cloud storage. For interface details, see WorkspaceSandbox interface.

Installation
Direct link to Installation

npm install @mastra/e2b

Usage
Direct link to Usage

Add an E2BSandbox to a workspace and assign it to an agent:

import { Agent } from '@mastra/core/agent'
import { Workspace } from '@mastra/core/workspace'
import { E2BSandbox } from '@mastra/e2b'

const workspace = new Workspace({
sandbox: new E2BSandbox({
id: 'dev-sandbox',
timeout: 60_000, // 60 second timeout (default: 5 minutes)
}),
})

const agent = new Agent({
id: 'dev-agent',
name: 'dev-agent',
model: 'anthropic/claude-opus-4-7',
workspace,
})

Constructor parameters
Direct link to Constructor parameters

apiKey?:

string
E2B API key. Falls back to E2B_API_KEY environment variable.

timeout?:

number
= 300000 (5 minutes)
Execution timeout in milliseconds

lifecycle?:

SandboxLifecycle
= { onTimeout: 'pause' }
Controls what happens when the sandbox timeout is reached. Defaults to pausing the sandbox so the next start resumes it. Pass { onTimeout: 'kill' } to destroy idle sandboxes instead, which suits stateless workspaces whose data is persisted outside the sandbox. An explicit stop() always pauses, regardless of this setting.

template?:

string | TemplateBuilder | function | NamedTemplateSpec
Sandbox template specification. Can be a template ID string, a TemplateBuilder, a function that customizes the default template, or a named spec such as the one returned by createRepoTemplate.

env?:

Record<string, string>
Environment variables to set in the sandbox

id?:

string
= Auto-generated
Unique identifier for this sandbox instance

sandboxId?:

string
Persisted E2B provider sandbox ID to reattach to deterministically. When set, start() connects to this exact sandbox (resuming it if paused) instead of discovering by logical id metadata. Only a typed "sandbox gone" error (not found, killed, or not running) falls through to the usual logical-id lookup and create ladder; auth, quota, rate-limit, timeout, and network errors propagate without creating a new sandbox. A sandbox tagged with a different logical id is refused. Read the resolved provider ID from the sandboxId property after start.

domain?:

string
Domain for self-hosted E2B. Falls back to E2B_DOMAIN env var.

apiUrl?:

string
API URL for self-hosted E2B. Falls back to E2B_API_URL env var.

accessToken?:

string
Access token for authentication. Falls back to E2B_ACCESS_TOKEN env var.

metadata?:

Record<string, unknown>
Custom metadata attached to the sandbox instance.

instructions?:

string | ((opts: { defaultInstructions: string; requestContext?: RequestContext }) => string)
Custom instructions returned by getInstructions(). A string fully replaces the defaults; a function receives the defaults and can extend or customize them per-request. Pass an empty string to suppress instructions entirely.

Properties
Direct link to Properties

id:

string
Sandbox instance identifier

name:

string
Provider name ('E2BSandbox')

provider:

string
Provider identifier ('e2b')

status:

ProviderStatus
'pending' | 'initializing' | 'ready' | 'starting' | 'running' | 'stopping' | 'stopped' | 'destroying' | 'destroyed' | 'error'

sandboxId:

string | undefined
The E2B provider sandbox ID resolved after connect or create. Persist it and pass it back via the sandboxId constructor option (or clone({ sandboxId })) to reattach deterministically. Undefined until the sandbox has been started in this process.

processes:

E2BProcessManager
Background process manager. See SandboxProcessManager reference.

Background processes
Direct link to Background processes

E2BSandbox includes a built-in process manager for spawning and managing background processes. Processes run in the E2B cloud sandbox using the E2B SDK's commands.run() with background: true.

const sandbox = new E2BSandbox({ id: 'dev-sandbox' })
await sandbox.start()

// Spawn a background process
const handle = await sandbox.processes.spawn('node server.js', {
env: { PORT: '3000' },
onStdout: data => console.log(data),
})

// Interact with the process
console.log(handle.stdout)
await handle.sendStdin('input\n')
await handle.kill()

The E2B process manager supports reconnecting to processes that were spawned externally or before a reconnection. Call get(pid) with a PID to connect to an existing process:

const handle = await sandbox.processes.get(existingPid)
if (handle) {
console.log(handle.stdout)
}

See SandboxProcessManager reference for the full API.

Mounting cloud storage
Direct link to Mounting cloud storage

E2B sandboxes can mount S3, GCS, and Azure Blob filesystems, making cloud storage accessible as local directories inside the sandbox. This is useful for:

  • Processing large datasets stored in cloud buckets
  • Writing output files directly to cloud storage
  • Sharing data between sandbox sessions

Using the mounts config
Direct link to Using the mounts config

The simplest way to mount filesystems is through the workspace mounts config:

import { Workspace } from '@mastra/core/workspace'
import { S3Filesystem } from '@mastra/s3'
import { GCSFilesystem } from '@mastra/gcs'
import { E2BSandbox } from '@mastra/e2b'

const workspace = new Workspace({
mounts: {
'/s3-data': new S3Filesystem({
bucket: 'my-s3-bucket',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
}),
'/gcs-data': new GCSFilesystem({
bucket: 'my-gcs-bucket',
projectId: 'my-project',
credentials: JSON.parse(process.env.GCS_SERVICE_ACCOUNT_KEY),
}),
},
sandbox: new E2BSandbox({ id: 'dev-sandbox' }),
})

When the sandbox starts, the filesystems are automatically mounted at the specified paths. Code running in the sandbox can then access files at /s3-data and /gcs-data as if they were local directories.

How mounting works
Direct link to How mounting works

E2B sandboxes use FUSE (Filesystem in Userspace) to mount cloud storage:

The E2B sandbox automatically installs the required FUSE tools when mounting is used. For best performance, pre-build a custom template with the tools installed.

Custom templates
Direct link to Custom templates

By default, when no template is specified, E2BSandbox automatically builds a template with s3fs installed for S3 mounting support, a current Node.js LTS installed over the base image's older runtime, and corepack enabled so pnpm and yarn resolve to whatever a repository's packageManager field pins. This template is cached and reused across sandbox instances.

The Node.js version is pinned exactly and is part of the template's identity. Pass nodeVersion to createDefaultMountableTemplate() to select a different release; changing it builds a new template.

For GCS mounting, gcsfuse is automatically installed at mount time if not already present. For additional tools or faster cold starts, use custom templates.

Using an existing template
Direct link to Using an existing template

If you have a pre-built template, pass its ID:

const workspace = new Workspace({
sandbox: new E2BSandbox({
id: 'dev-sandbox',
template: 'my-custom-template',
}),
})

Customizing the default template
Direct link to Customizing the default template

Pass a function to customize the default mountable template. The function receives a TemplateBuilder and should return the modified template:

const workspace = new Workspace({
sandbox: new E2BSandbox({
template: base =>
base
.aptInstall(['ffmpeg', 'imagemagick', 'poppler-utils'])
.pipInstall(['pandas', 'numpy'])
.npmInstall(['sharp']),
}),
})

The template builder supports method chaining with operations like:

  • aptInstall(packages) - Install system packages
  • pipInstall(packages) - Install Python packages
  • npmInstall(packages) - Install Node.js packages
  • runCmd(command) - Run shell commands
  • setEnvs(vars) - Set environment variables
  • copy(src, dest) - Copy files into the template

See E2B's template documentation for the full list of available methods.

Pre-building templates
Direct link to Pre-building templates

The default template is built on first use and cached. For faster cold starts or to include GCS support, you can pre-build a template:

import { createDefaultMountableTemplate } from '@mastra/e2b'
import { Template } from 'e2b'

// Get the default mountable template (includes s3fs)
const { template, id } = createDefaultMountableTemplate()

// Build and save to E2B
const result = await Template.build(template, id)
console.log('Template ID:', result.templateId)

// Use this ID in your E2BSandbox config for instant startup
const sandbox = new E2BSandbox({
template: result.templateId,
})

For faster GCS cold starts, pre-install gcsfuse in a custom template:

const workspace = new Workspace({
sandbox: new E2BSandbox({
id: 'dev-sandbox',
template: base => base.aptInstall(['gcsfuse']),
}),
})

This is optional: gcsfuse is installed automatically at mount time if not present.

Repository templates
Direct link to Repository templates

createRepoTemplate produces a template spec that clones a repository and runs its setup command at build time, so sandboxes start with a warm checkout and installed dependencies instead of paying for a cold clone on every session:

import { E2BSandbox, createRepoTemplate } from '@mastra/e2b'

const sandbox = new E2BSandbox({
id: sessionId,
template: createRepoTemplate({
getRepositoryAccess: async () => ({
cloneUrl: 'https://github.com/octocat/hello-world.git',
}),
setupCommand: 'pnpm install',
}),
})

getRepositoryAccess supplies the clone URL and, for private repositories, a credential. It's the only source of the clone URL, so what gets cloned and what the template is identified by can't drift apart. When it's undefined, createRepoTemplate returns undefined — which is how a session with no repository asks for the provider's default template without a conditional at the call site.

There is exactly one template per repository and setup command: the template name carries the repo slug plus a short hash of the inputs, and the commit sha rides as a tag on that name (mastra-repo-<owner>-<repo>-<hash>:sha-<sha>). The spec pins itself to the repository's current default-branch head at resolution time: right before the template lookup it runs git ls-remote (no clone, ~100ms) and keys the tag on that sha. When the default branch moves, the next new sandbox rebuilds the same template in place under a new tag, and old sha tags remain as prunable build history instead of piling up as stale templates. If the head can't be resolved, the ref degrades to the stable current tag and the build clones whatever the default branch is at build time.

Resolution is lazy and only ever blocks on a template's very first build. Every successful build also moves a stable current tag, so when the head moves, the next sandbox boots immediately from the previous build while the fresh sha ref builds in the background on E2B's side (its runtime setup git fetch fast-forwards the slightly stale checkout — freshness never depends on the template). A changed setup command hashes to a new template name.

Templates build at E2B's default machine size (2 vCPU, 1024 MB) unless the spec asks for more. Pass cpuCount and memoryMB to size the machine the template's sandboxes run on:

const sandbox = new E2BSandbox({
id: sessionId,
template: createRepoTemplate({
getRepositoryAccess: async () => ({
cloneUrl: 'https://github.com/octocat/hello-world.git',
}),
setupCommand: 'pnpm install',
cpuCount: 4,
memoryMB: 2048,
}),
})

Resources are part of the template's identity: they hash into the template name alongside the repository and setup command, so resizing builds a new template instead of silently reusing one built at the old size. When a repo template's build fails and the sandbox falls back to the default mountable template, the fallback builds at the requested size too, so setup never lands in a smaller machine than it asked for. Account tier limits apply to the values E2B accepts.

To warm templates proactively instead of waiting for the first session after a merge, call refreshRepoTemplate with the same options from a scheduled job or a merge-to-main event handler. It performs the same resolution as sandbox start — resolves the current head, reuses the build when it already exists, and otherwise builds it (moving current) — and reports { ref, action, sha }:

import { refreshRepoTemplate } from '@mastra/e2b'

const result = await refreshRepoTemplate({
getRepositoryAccess: async () => ({
cloneUrl: 'https://github.com/octocat/hello-world.git',
}),
setupCommand: 'pnpm install',
})
// { ref: 'mastra-repo-octocat-hello-world-…:sha-…', action: 'built' | 'reused', sha: '…' }

The template layers on top of the default mountable base (E2B's base image, so git and common tooling are present, plus s3fs/FUSE for mount support). The clone lands in the build user's home directory at $HOME/<repo>, derived from the clone URL. Build steps and runtime commands both run as the sandbox's non-root user in its home, so no directory prep is needed, runtime file ownership stays correct, and a runtime $HOME probe finds the checkout exactly where the template left it.

Failure handling is designed so a broken template never wedges a session:

  • A failed build falls back to the default mountable template; the session's runtime cold clone into $HOME keeps working.
  • E2B keeps a failed build's ref visible to Template.exists, so a broken ref could otherwise be reused forever. When creating a sandbox from a named ref fails, the cached resolution is dropped and creation retries down the fallback ladder.

Private repositories build warm templates when getRepositoryAccess returns an authorization alongside the clone URL — a fresh, short-lived credential (such as a GitHub App installation token) minted per template resolution. The credential authenticates the head lookup and the build's clone through an in-shell http.extraheader — it enters the template definition's environment (visible to build steps but not persisted into runtime sandbox environments) and never touches the image filesystem, so no captured layer can contain it. It's set as GH_TOKEN, the same variable a session installs before running setup, so a command that shells out to gh or authenticated https behaves the same in the build as it does in a session.

Only pass short-lived credentials — the value stays in the template definition until the next rebuild, so its expiry is what bounds the exposure. Note that the cloned repository contents become part of a team-visible template image. When the access call returns no credential the clone is tokenless: public repositories build fine, and a private repository's build degrades to the fallback, where the session's own runtime setup (with runtime-injected auth) does the clone.

Setup commands that need their own credentials — a registry token, a private index URL — take them through buildEnv, which accepts a record or an async resolver. Those values are part of the template's identity, so changing one produces a new template.

repoTemplateRef computes the tagged ref for a spec without building anything — useful for pruning old sha tags with the E2B CLI.

Per-session sandboxes
Direct link to Per-session sandboxes

Hosts that construct one sandbox per session, such as Mastra Factory's sandbox config, combine the pieces above in a small callback: sandbox identity is the session id (id-keyed getOrCreate — the sandbox pauses on E2B's idle timeout and resumes by id), and the repo template comes from createRepoTemplate. The host's session context carries everything the template needs, so it can be passed straight through:

import { MastraFactory } from '@mastra/factory'
import { E2BSandbox, createRepoTemplate } from '@mastra/e2b'

new MastraFactory({
sandbox: ctx =>
new E2BSandbox({
id: ctx.sessionId,
// Undefined for a session with no repository.
template: createRepoTemplate(ctx),
}),
})

Factory attaches its own session setup to the sandbox it gets back, so the callback doesn't wire up a start hook.

Using with Code Mode
Direct link to Using with Code Mode

Code Mode lets an agent write a single TypeScript program that orchestrates its tools. Because E2B runs that program in a remote micro-VM, it needs a transport that writes the program into the sandbox filesystem rather than the host. @mastra/e2b provides E2BCodeModeTransport for this. Pass it as the second argument to createCodeMode:

import { createCodeMode } from '@mastra/core/tools'
import { E2BSandbox, E2BCodeModeTransport } from '@mastra/e2b'

const { tool, instructions } = createCodeMode(
{
tools: { getWeather, getForecast },
sandbox: new E2BSandbox({ timeout: 60_000 }),
},
new E2BCodeModeTransport(),
)

E2BCodeModeTransport auto-starts the sandbox if it isn't running, strips TypeScript on the host with esbuild (so it works regardless of the sandbox's Node version), runs node inside the VM, and cleans up the program files afterwards. The default StdioCodeModeTransport from @mastra/core only works with sandboxes that share the host filesystem, such as LocalSandbox.