Sandbox Providers
Runra Runtime abstracts sandbox infrastructure behind a pluggable provider interface. Choose hosted, self-hosted, or local providers — your agent code stays the same.
Built-in Providers
Section titled “Built-in Providers”| Provider | Key | Use Case |
|---|---|---|
| Runra Sandbox | runra-sandbox | Hosted VM-backed sandboxes (production) |
| CubeSandbox | cubesandbox | Self-hosted VM-backed sandboxes |
| Docker | docker | Local Docker containers (development) |
| E2B | e2b | E2B sandbox infrastructure |
Runra Sandbox (Hosted)
Section titled “Runra Sandbox (Hosted)”The recommended provider for production. Fully managed, VM-backed isolation, no infrastructure to maintain.
import { Runra } from "@runra/runtime";
const runra = new Runra({ sandbox: { provider: "runra-sandbox", apiKey: process.env.RUNRA_API_KEY, apiUrl: "https://api.box.runra.dev", // optional, default defaults: { image: "node:22", resources: { cpu: 2, memoryMb: 4096, diskMb: 10240 }, timeoutMs: 300_000, idleTimeoutMs: 120_000, }, }, // ... agent and observability config});Available Images
Section titled “Available Images”| Image | Description |
|---|---|
node:22 | Node.js 22 with npm, yarn, pnpm |
node:20 | Node.js 20 LTS |
python:3.12 | Python 3.12 with pip, uv |
python:3.11 | Python 3.11 |
ubuntu:24.04 | Bare Ubuntu 24.04 |
ubuntu:22.04 | Bare Ubuntu 22.04 |
custom | BYO image (contact support) |
Features
Section titled “Features”- VM-level isolation per sandbox
- Pause & resume preserves full filesystem state
- Configurable network policies
- Automatic cleanup via
timeoutMsandidleTimeoutMs - Up to 8 vCPUs and 16 GB RAM per sandbox
- Expose ports for previews and dev servers
- Full API reference
CubeSandbox (Self-Hosted)
Section titled “CubeSandbox (Self-Hosted)”Run sandboxes on your own infrastructure using the open-source CubeSandbox engine. Ideal for air-gapped environments or compliance requirements.
const runra = new Runra({ sandbox: { provider: "cubesandbox", config: { masterUrl: "https://cube-master.internal:3000", masterToken: process.env.CUBE_MASTER_TOKEN, templates: { node: "template-node-22", python: "template-python-312", }, nodeSelector: { zone: "us-east-1", }, }, defaults: { image: "node:22", resources: { cpu: 2, memoryMb: 4096 }, }, },});Self-hosting CubeSandbox requires:
- A CubeMaster control plane
- One or more Cubelet compute nodes
- Image templates built on each node
See the CubeSandbox repository for setup instructions.
Docker (Local Development)
Section titled “Docker (Local Development)”Use local Docker containers for development and testing. No API key required.
const runra = new Runra({ sandbox: { provider: "docker", config: { socketPath: "/var/run/docker.sock", // default // Or remote Docker: // host: "tcp://192.168.1.100:2375", images: { node: "node:22-alpine", python: "python:3.12-slim", ubuntu: "ubuntu:24.04", }, networkMode: "bridge", // "bridge" or "host" autoRemove: true, // Clean up containers }, defaults: { image: "node:22", resources: { cpu: 2, memoryMb: 2048 }, }, },});Resource Mapping
Section titled “Resource Mapping”Docker provider maps Runra resources to Docker limits:
| Runra | Docker |
|---|---|
cpu: 2 | --cpus=2 |
memoryMb: 4096 | --memory=4096m |
diskMb: 10240 | --storage-opt size=10G |
timeoutMs: 300000 | --stop-timeout |
Limitations
Section titled “Limitations”- No VM-level isolation (shared kernel)
- No pause/resume (simulated via
docker pausewhich freezes processes) - File system not preserved on terminate
- Suitable for development only, not production
Use E2B’s sandbox infrastructure:
const runra = new Runra({ sandbox: { provider: "e2b", config: { apiKey: process.env.E2B_API_KEY, domain: "e2b.dev", // optional template: "base", }, defaults: { image: "node:22", timeoutMs: 300_000, }, },});Provider Configuration
Section titled “Provider Configuration”interface SandboxConfig { /** Provider key */ provider: "runra-sandbox" | "cubesandbox" | "docker" | "e2b" | "custom";
/** API key for hosted providers */ apiKey?: string;
/** Provider-specific configuration */ config?: Record<string, unknown>;
/** Default sandbox settings applied to all creates */ defaults?: { image?: string; resources?: { cpu?: number; memoryMb?: number; diskMb?: number; }; timeoutMs?: number; idleTimeoutMs?: number; env?: Record<string, string>; workdir?: string; network?: { egress?: { allow?: string[]; default?: "allow" | "deny"; }; }; };}Custom Provider
Section titled “Custom Provider”Implement the SandboxProvider interface to integrate any sandbox infrastructure:
import type { SandboxProvider, SandboxHandle, SandboxConfig, ExecuteOptions, ExecuteResult } from "@runra/runtime";
interface MyProviderConfig { clusterUrl: string; authToken: string; namespace: string;}
class MySandboxProvider implements SandboxProvider { readonly id = "my-provider";
private config!: MyProviderConfig;
async initialize(config: Record<string, unknown>): Promise<void> { this.config = config as unknown as MyProviderConfig; }
async create(config: SandboxConfig): Promise<SandboxHandle> { const response = await fetch(`${this.config.clusterUrl}/api/v1/sandboxes`, { method: "POST", headers: { "Authorization": `Bearer ${this.config.authToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ image: config.image, cpu: config.resources?.cpu ?? 2, memoryMb: config.resources?.memoryMb ?? 4096, namespace: this.config.namespace, }), });
const data = await response.json();
return { id: data.id, state: "running", image: config.image, resources: config.resources || { cpu: 2, memoryMb: 4096 }, createdAt: data.createdAt, }; }
async get(id: string): Promise<SandboxHandle> { const response = await fetch( `${this.config.clusterUrl}/api/v1/sandboxes/${id}`, { headers: { Authorization: `Bearer ${this.config.authToken}` } } ); const data = await response.json(); return { id: data.id, state: data.state, image: data.image, createdAt: data.createdAt }; }
async list(opts?: { state?: string; limit?: number }): Promise<SandboxHandle[]> { const url = new URL(`${this.config.clusterUrl}/api/v1/sandboxes`); if (opts?.state) url.searchParams.set("state", opts.state); if (opts?.limit) url.searchParams.set("limit", String(opts.limit));
const response = await fetch(url, { headers: { Authorization: `Bearer ${this.config.authToken}` }, });
const data = await response.json(); return data.items.map((item: any) => ({ id: item.id, state: item.state, image: item.image, createdAt: item.createdAt, })); }
async execute(id: string, command: string, opts?: ExecuteOptions): Promise<ExecuteResult> { const response = await fetch( `${this.config.clusterUrl}/api/v1/sandboxes/${id}/exec`, { method: "POST", headers: { "Authorization": `Bearer ${this.config.authToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ command, cwd: opts?.cwd, env: opts?.env, timeoutMs: opts?.timeoutMs, }), } );
const data = await response.json(); return { stdout: data.stdout, stderr: data.stderr, exitCode: data.exitCode, durationMs: data.durationMs, signal: data.signal ?? null, }; }
async pause(id: string): Promise<void> { await fetch( `${this.config.clusterUrl}/api/v1/sandboxes/${id}/pause`, { method: "POST", headers: { Authorization: `Bearer ${this.config.authToken}` }, } ); }
async resume(id: string): Promise<void> { await fetch( `${this.config.clusterUrl}/api/v1/sandboxes/${id}/resume`, { method: "POST", headers: { Authorization: `Bearer ${this.config.authToken}` }, } ); }
async terminate(id: string): Promise<void> { await fetch( `${this.config.clusterUrl}/api/v1/sandboxes/${id}`, { method: "DELETE", headers: { Authorization: `Bearer ${this.config.authToken}` }, } ); }
async writeFile(id: string, path: string, content: string): Promise<void> { await fetch( `${this.config.clusterUrl}/api/v1/sandboxes/${id}/files`, { method: "POST", headers: { "Authorization": `Bearer ${this.config.authToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ path, content }), } ); }
async readFile(id: string, path: string): Promise<string> { const response = await fetch( `${this.config.clusterUrl}/api/v1/sandboxes/${id}/files?path=${encodeURIComponent(path)}`, { headers: { Authorization: `Bearer ${this.config.authToken}` } } ); const data = await response.json(); return data.content; }
async dispose(): Promise<void> {}}
// Register and useRunra.registerSandboxProvider("my-provider", () => new MySandboxProvider());Provider Comparison
Section titled “Provider Comparison”| Feature | Runra Sandbox | CubeSandbox | Docker | E2B |
|---|---|---|---|---|
| Isolation | VM | VM | Container | VM |
| Pause/Resume | ✅ | ✅ | ⚠️ Limited | ✅ |
| Network Control | ✅ | ✅ | ✅ | ✅ |
| Port Exposure | ✅ | ✅ | ✅ | ✅ |
| Production Ready | ✅ | ✅ | ❌ | ✅ |
| Self-Hosted | ❌ | ✅ | ✅ | ❌ |
| Free Tier | ✅ | ✅ | ✅ | ❌ |
Switching Providers
Section titled “Switching Providers”Switch providers without changing agent or observability code:
// Development: local Dockerconst devRunra = new Runra({ sandbox: { provider: "docker" }, // ... same agent config});
// Production: hosted Runraconst prodRunra = new Runra({ sandbox: { provider: "runra-sandbox", apiKey: "..." }, // ... same agent config});
// Self-hosted: CubeSandboxconst selfRunra = new Runra({ sandbox: { provider: "cubesandbox", config: { masterUrl: "..." } }, // ... same agent config});Next Steps
Section titled “Next Steps”- Runtime Architecture — how providers fit in
- Observability — export sandbox events
- Configuration Reference — all options