Docs / runtime / sandbox-providers

Sandbox Providers

Sandbox infrastructure providers for Runra Runtime — Runra Sandbox (hosted), CubeSandbox (self-hosted), Docker, E2B, and custom provider interface

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

ProviderKeyUse Case
Runra Sandboxrunra-sandboxHosted VM-backed sandboxes (production)
CubeSandboxcubesandboxSelf-hosted VM-backed sandboxes
DockerdockerLocal Docker containers (development)
E2Be2bE2B sandbox infrastructure

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

ImageDescription
node:22Node.js 22 with npm, yarn, pnpm
node:20Node.js 20 LTS
python:3.12Python 3.12 with pip, uv
python:3.11Python 3.11
ubuntu:24.04Bare Ubuntu 24.04
ubuntu:22.04Bare Ubuntu 22.04
customBYO image (contact support)

Features

  • VM-level isolation per sandbox
  • Pause & resume preserves full filesystem state
  • Configurable network policies
  • Automatic cleanup via timeoutMs and idleTimeoutMs
  • Up to 8 vCPUs and 16 GB RAM per sandbox
  • Expose ports for previews and dev servers
  • Full API reference

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 },
    },
  },
});

Setup

Self-hosting CubeSandbox requires:

  1. A CubeMaster control plane
  2. One or more Cubelet compute nodes
  3. Image templates built on each node

See the CubeSandbox repository for setup instructions.

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

Docker provider maps Runra resources to Docker limits:

RunraDocker
cpu: 2--cpus=2
memoryMb: 4096--memory=4096m
diskMb: 10240--storage-opt size=10G
timeoutMs: 300000--stop-timeout

Limitations

  • No VM-level isolation (shared kernel)
  • No pause/resume (simulated via docker pause which freezes processes)
  • File system not preserved on terminate
  • Suitable for development only, not production

E2B

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

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

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 use
Runra.registerSandboxProvider("my-provider", () => new MySandboxProvider());

Provider Comparison

FeatureRunra SandboxCubeSandboxDockerE2B
IsolationVMVMContainerVM
Pause/Resume⚠️ Limited
Network Control
Port Exposure
Production Ready
Self-Hosted
Free Tier

Switching Providers

Switch providers without changing agent or observability code:

// Development: local Docker
const devRunra = new Runra({
  sandbox: { provider: "docker" },
  // ... same agent config
});

// Production: hosted Runra
const prodRunra = new Runra({
  sandbox: { provider: "runra-sandbox", apiKey: "..." },
  // ... same agent config
});

// Self-hosted: CubeSandbox
const selfRunra = new Runra({
  sandbox: { provider: "cubesandbox", config: { masterUrl: "..." } },
  // ... same agent config
});

Next Steps