Skip to main content

Overview

Sandboxes are the foundation of RunTools. Each sandbox is an isolated microVM that provides:
  • Full Linux environment with root access
  • Flexible mounts — ephemeral by default, add persistent storage as needed
  • Network access for dev servers and APIs
  • Pause/Resume with instant snapshot restore
  • SSH and VNC access for debugging

Creating a Sandbox

const sandbox = await rt.sandboxes.create({
  template: 'nodejs-20',
  idleTimeout: 600, // Auto-pause after 10 min
});

Mounts

By default, sandboxes use ephemeral storage — fast and simple, but data is lost when the sandbox is destroyed. For persistent data, add mounts:
// No mounts (default) — ephemeral, data lost on destroy
const sandbox = await rt.sandboxes.create({
  template: 'nodejs-20',
});

// Single mount — persistent storage at /workspace
const sandbox = await rt.sandboxes.create({
  template: 'nodejs-20',
  mounts: [
    { workspaceId: 'my-project', path: '/workspace' },
  ],
});

// Multiple mounts — code and data in different locations
const sandbox = await rt.sandboxes.create({
  template: 'nodejs-20',
  mounts: [
    { workspaceId: 'my-code', path: '/workspace' },
    { workspaceId: 'datasets', path: '/data' },
  ],
});
ConfigBehavior
No mountsEphemeral. Fast startup. Data deleted when sandbox destroyed.
With mountsPersistent. Data survives across sandbox lifecycle.
Mounts are scoped to your account. The same workspaceId always contains the same data, regardless of which sandbox or template you use. Mount it at any path you want.

Templates

Choose from pre-built templates or create your own:
TemplateDescriptionSize
nodejs-20Node.js 20, Bun, pnpm450MB
python-mlPython 3.12, PyTorch, NumPy2.1GB
full-stackNode + Python + PostgreSQL1.2GB
go-1.22Go 1.22 with common tools380MB
rust-1.75Rust with Cargo520MB

Custom Templates

Learn how to create custom templates

Executing Commands

// Simple execution
const result = await sandbox.exec('ls -la');
console.log(result.stdout);
console.log(result.exitCode);

// With timeout
const result = await sandbox.exec('npm run build', { 
  timeout: 120000 // 2 minutes
});

// Stream output
for await (const chunk of sandbox.execStream('npm run dev')) {
  process.stdout.write(chunk);
}

File Operations

// Write files
await sandbox.files.write('/src/app.ts', `
import express from 'express';
const app = express();
app.listen(3000);
`);

// Read files
const content = await sandbox.files.read('/src/app.ts');

// List directory
const files = await sandbox.files.list('/src');
// → [{ name: 'app.ts', type: 'file', size: 123 }, ...]

// Delete files
await sandbox.files.remove('/src/old.ts');

// Surgical edit
await sandbox.files.replace('/src/app.ts', {
  old: 'const port = 3000',
  new: 'const port = process.env.PORT || 3000',
});

Dev Server URLs

Get a public URL for your dev server:
// Start a dev server
await sandbox.exec('npm run dev &');

// Get the public URL
const url = await sandbox.getUrl({ port: 3000 });
console.log(url);
// → https://sandbox-abc123.sandboxes.runtools.ai
URLs are automatically routed through our Caddy reverse proxy. They support HTTPS and work with any framework (Vite, Next.js, etc).

Pause and Resume

Save money by pausing sandboxes when not in use:
// Pause - snapshots entire VM state to disk
await sandbox.pause();
// Billing stops, state is preserved

// Resume - restores in under 1 second
await sandbox.resume();
// Everything is exactly as you left it

Idle Timeout

Configure automatic pausing:
const sandbox = await rt.sandboxes.create({
  template: 'nodejs-20',
  idleTimeout: 600, // Pause after 10 minutes of inactivity
});

Snapshots and Branching

Snapshots are different from pause/resume. They create named checkpoints you can return to or branch from.
// Create a named snapshot
await sandbox.snapshot({ name: 'before-refactor' });

// Make some changes...
await sandbox.exec('rm -rf src && mkdir src');

// Oops! Rollback
await sandbox.rollback({ snapshot: 'before-refactor' });

// Or branch into a new sandbox
const branched = await sandbox.branch({
  snapshot: 'before-refactor',
  name: 'experiment',
});

SSH Access

SSH access uses your registered SSH keys. First, register your public key:
# Register your SSH key (one-time setup)
runtools ssh-key add my-laptop
# → Reads ~/.ssh/id_ed25519.pub (or id_rsa.pub) automatically
Then connect to any sandbox:
# SSH into sandbox (uses your registered key)
runtools sandbox ssh sandbox-abc123

# Or directly via SSH gateway
ssh [email protected]
The SSH gateway (ssh.runtools.ai) uses your sandbox ID as the username and automatically routes to the correct VM. Your local private key is matched against your org’s registered public keys.

VNC Access (Desktop)

For GUI applications and computer use:
const vnc = await sandbox.getVNC();
console.log(vnc.url);      // wss://sandbox-abc123.vnc.runtools.ai
console.log(vnc.password); // Connection password

Lifecycle Management

// List all sandboxes
const sandboxes = await rt.sandboxes.list();

// Filter by status
const running = await rt.sandboxes.list({ status: 'running' });

// Get a specific sandbox
const sandbox = await rt.sandboxes.get('sandbox-abc123');

// Destroy when done
await sandbox.destroy();

Resource Limits

Each sandbox has configurable resource limits:
ResourceDefaultMax
vCPUs28
Memory2GB16GB
Disk10GB100GB
Network1Gbps1Gbps
const sandbox = await rt.sandboxes.create({
  template: 'python-ml',
  resources: {
    vcpus: 4,
    memory: '8GB',
    disk: '50GB',
  },
});

Best Practices

Paused sandboxes don’t incur compute costs. Set an idleTimeout to auto-pause.
Create a snapshot before refactoring or major changes. Easy to rollback.
Destroy sandboxes you no longer need. Storage costs apply even when paused.
Don’t use python-ml (2GB) if you just need Node.js (450MB). Faster startup, lower cost.