update skills

This commit is contained in:
2026-03-17 16:53:22 -07:00
parent 0b0783ef8e
commit f9a530667e
389 changed files with 54512 additions and 1 deletions

View File

@@ -0,0 +1,96 @@
# Cloudflare Sandbox SDK
Secure isolated code execution in containers on Cloudflare's edge. Run untrusted code, manage files, expose services, integrate with AI agents.
**Use cases**: AI code execution, interactive dev environments, data analysis, CI/CD, code interpreters, multi-tenant execution.
## Architecture
- Each sandbox = Durable Object + Container
- Persistent across requests (same ID = same sandbox)
- Isolated filesystem/processes/network
- Configurable sleep/wake for cost optimization
## Quick Start
```typescript
import { getSandbox, proxyToSandbox, type Sandbox } from '@cloudflare/sandbox';
export { Sandbox } from '@cloudflare/sandbox';
type Env = { Sandbox: DurableObjectNamespace<Sandbox>; };
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// CRITICAL: proxyToSandbox MUST be called first for preview URLs
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
const result = await sandbox.exec('python3 -c "print(2 + 2)"');
return Response.json({ output: result.stdout });
}
};
```
**wrangler.jsonc**:
```jsonc
{
"name": "my-sandbox-worker",
"main": "src/index.ts",
"compatibility_date": "2025-01-01", // Use current date for new projects
"containers": [{
"class_name": "Sandbox",
"image": "./Dockerfile",
"instance_type": "lite", // lite | standard | heavy
"max_instances": 5
}],
"durable_objects": {
"bindings": [{ "class_name": "Sandbox", "name": "Sandbox" }]
},
"migrations": [{
"tag": "v1",
"new_sqlite_classes": ["Sandbox"]
}]
}
```
**Dockerfile**:
```dockerfile
FROM docker.io/cloudflare/sandbox:latest
RUN pip3 install --no-cache-dir pandas numpy matplotlib
EXPOSE 8080 3000 # Required for wrangler dev
```
## Core APIs
- `getSandbox(namespace, id, options?)` → Get/create sandbox
- `sandbox.exec(command, options?)` → Execute command
- `sandbox.readFile(path)` / `writeFile(path, content)` → File ops
- `sandbox.startProcess(command, options)` → Background process
- `sandbox.exposePort(port, options)` → Get preview URL
- `sandbox.createSession(options)` → Isolated session
- `sandbox.wsConnect(request, port)` → WebSocket proxy
- `sandbox.destroy()` → Terminate container
- `sandbox.mountBucket(bucket, path, options)` → Mount S3 storage
## Critical Rules
- ALWAYS call `proxyToSandbox()` first
- Same ID = reuse sandbox
- Use `/workspace` for persistent files
- `normalizeId: true` for preview URLs
- Retry on `CONTAINER_NOT_READY`
## In This Reference
- [configuration.md](./configuration.md) - Config, CLI, environment setup
- [api.md](./api.md) - Programmatic API, testing patterns
- [patterns.md](./patterns.md) - Common workflows, CI/CD integration
- [gotchas.md](./gotchas.md) - Issues, limits, best practices
## See Also
- [durable-objects](../durable-objects/) - Sandbox runs on DO infrastructure
- [containers](../containers/) - Container runtime fundamentals
- [workers](../workers/) - Entry point for sandbox requests

View File

@@ -0,0 +1,198 @@
# API Reference
## Command Execution
```typescript
// Basic
const result = await sandbox.exec('python3 script.py');
// Returns: { stdout, stderr, exitCode, success, duration }
// With options
await sandbox.exec('python3 test.py', {
cwd: '/workspace/project',
env: { API_KEY: 'secret' },
stream: true,
onOutput: (stream, data) => console.log(data)
});
```
## File Operations
```typescript
// Read/Write
const { content } = await sandbox.readFile('/workspace/data.txt');
await sandbox.writeFile('/workspace/file.txt', 'content'); // Auto-creates dirs
// List/Delete
const files = await sandbox.listFiles('/workspace');
await sandbox.deleteFile('/workspace/temp.txt');
await sandbox.deleteFile('/workspace/dir', { recursive: true });
// Utils
await sandbox.mkdir('/workspace/dir', { recursive: true });
await sandbox.pathExists('/workspace/file.txt');
```
## Background Processes
```typescript
// Start
const process = await sandbox.startProcess('python3 -m http.server 8080', {
processId: 'web-server',
cwd: '/workspace/public',
env: { PORT: '8080' }
});
// Returns: { id, pid, command }
// Wait for readiness
await process.waitForPort(8080); // Wait for port to listen
await process.waitForLog(/Server running/); // Wait for log pattern
await process.waitForExit(); // Wait for completion
// Management
const processes = await sandbox.listProcesses();
const info = await sandbox.getProcess('web-server');
await sandbox.stopProcess('web-server');
const logs = await sandbox.getProcessLogs('web-server');
```
## Port Exposure
```typescript
// Expose port
const { url } = await sandbox.exposePort(8080, {
name: 'web-app',
hostname: request.hostname
});
// Management
await sandbox.isPortExposed(8080);
await sandbox.getExposedPorts(request.hostname);
await sandbox.unexposePort(8080);
```
## Sessions (Isolated Contexts)
Each session maintains own shell state, env vars, cwd, process namespace.
```typescript
// Create with context
const session = await sandbox.createSession({
id: 'user-123',
cwd: '/workspace/user123',
env: { USER_ID: '123' }
});
// Use (full sandbox API)
await session.exec('echo $USER_ID');
await session.writeFile('config.txt', 'data');
// Manage
await sandbox.getSession('user-123');
await sandbox.deleteSession('user-123');
```
## Code Interpreter
```typescript
// Create context with variables
const ctx = await sandbox.createCodeContext({
language: 'python',
variables: {
data: [1, 2, 3, 4, 5],
config: { verbose: true }
}
});
// Execute code with rich outputs
const result = await ctx.runCode(`
import matplotlib.pyplot as plt
plt.plot(data, [x**2 for x in data])
plt.savefig('plot.png')
print(f"Processed {len(data)} points")
`);
// Returns: { outputs: [{ type: 'text'|'image'|'html', content }], error }
// Context persists variables across runs
const result2 = await ctx.runCode('print(data[0])'); // Still has 'data'
```
## WebSocket Connections
```typescript
// Proxy WebSocket to sandbox service
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
const sandbox = getSandbox(env.Sandbox, 'realtime');
return await sandbox.wsConnect(request, 8080);
}
return new Response('Not a WebSocket request', { status: 400 });
}
};
```
## Bucket Mounting (S3 Storage)
```typescript
// Mount R2 bucket (production only, not wrangler dev)
await sandbox.mountBucket(env.DATA_BUCKET, '/data', {
readOnly: false
});
// Access files in mounted bucket
await sandbox.exec('ls /data');
await sandbox.writeFile('/data/output.txt', 'result');
// Unmount
await sandbox.unmountBucket('/data');
```
**Note**: Bucket mounting only works in production. Mounted buckets are sandbox-scoped (visible to all sessions in that sandbox).
## Lifecycle Management
```typescript
// Terminate container immediately
await sandbox.destroy();
// REQUIRED when using keepAlive: true
const sandbox = getSandbox(env.Sandbox, 'temp', { keepAlive: true });
try {
await sandbox.writeFile('/tmp/code.py', code);
const result = await sandbox.exec('python /tmp/code.py');
return result.stdout;
} finally {
await sandbox.destroy(); // Free resources
}
```
Deletes: files, processes, sessions, network connections, exposed ports.
## Error Handling
```typescript
// Command errors
const result = await sandbox.exec('python3 invalid.py');
if (!result.success) {
console.error('Exit code:', result.exitCode);
console.error('Stderr:', result.stderr);
}
// SDK errors
try {
await sandbox.readFile('/nonexistent');
} catch (error) {
if (error.code === 'FILE_NOT_FOUND') { /* ... */ }
else if (error.code === 'CONTAINER_NOT_READY') { /* retry */ }
else if (error.code === 'TIMEOUT') { /* ... */ }
}
// Retry pattern (see gotchas.md for full implementation)
```

View File

@@ -0,0 +1,143 @@
# Configuration
## getSandbox Options
```typescript
const sandbox = getSandbox(env.Sandbox, 'sandbox-id', {
normalizeId: true, // lowercase ID (required for preview URLs)
sleepAfter: '10m', // sleep after inactivity: '5m', '1h', '2d' (default: '10m')
keepAlive: false, // false = auto-timeout, true = never sleep
containerTimeouts: {
instanceGetTimeoutMS: 30000, // 30s for provisioning (default: 30000)
portReadyTimeoutMS: 90000 // 90s for container startup (default: 90000)
}
});
```
**Sleep Config**:
- `sleepAfter`: Duration string (e.g., '5m', '10m', '1h') - default: '10m'
- `keepAlive: false`: Auto-sleep (default, cost-optimized)
- `keepAlive: true`: Never sleep (higher cost, requires explicit `destroy()`)
- Sleeping sandboxes wake automatically (cold start)
## Instance Types
wrangler.jsonc `instance_type`:
- `lite`: 256MB RAM, 0.5 vCPU (default)
- `standard`: 512MB RAM, 1 vCPU
- `heavy`: 1GB RAM, 2 vCPU
## Dockerfile Patterns
**Basic**:
```dockerfile
FROM docker.io/cloudflare/sandbox:latest
RUN pip3 install --no-cache-dir pandas numpy
EXPOSE 8080 # Required for wrangler dev
```
**Scientific**:
```dockerfile
FROM docker.io/cloudflare/sandbox:latest
RUN pip3 install --no-cache-dir \
jupyter-server ipykernel matplotlib \
pandas seaborn plotly scipy scikit-learn
```
**Node.js**:
```dockerfile
FROM docker.io/cloudflare/sandbox:latest
RUN npm install -g typescript ts-node
```
**CRITICAL**: `EXPOSE` required for `wrangler dev` port access. Production auto-exposes all ports.
## CLI Commands
```bash
# Dev
wrangler dev # Start local dev server
wrangler deploy # Deploy to production
wrangler tail # Monitor logs
wrangler containers list # Check container status
wrangler secret put KEY # Set secret
```
## Environment & Secrets
**wrangler.jsonc**:
```jsonc
{
"vars": {
"ENVIRONMENT": "production",
"API_URL": "https://api.example.com"
},
"r2_buckets": [{
"binding": "DATA_BUCKET",
"bucket_name": "my-data-bucket"
}]
}
```
**Usage**:
```typescript
const token = env.GITHUB_TOKEN; // From wrangler secret
await sandbox.exec('git clone ...', {
env: { GIT_TOKEN: token }
});
```
## Preview URL Setup
**Prerequisites**:
- Custom domain with wildcard DNS: `*.yourdomain.com → worker.yourdomain.com`
- `.workers.dev` domains NOT supported
- `normalizeId: true` in getSandbox
- `proxyToSandbox()` called first in fetch handler
## Cron Triggers (Pre-warming)
```jsonc
{
"triggers": {
"crons": ["*/5 * * * *"] // Every 5 minutes
}
}
```
```typescript
export default {
async scheduled(event: ScheduledEvent, env: Env) {
const sandbox = getSandbox(env.Sandbox, 'main');
await sandbox.exec('echo "keepalive"'); // Wake sandbox
}
};
```
## Logging Configuration
**wrangler.jsonc**:
```jsonc
{
"vars": {
"SANDBOX_LOG_LEVEL": "debug", // debug | info | warn | error (default: info)
"SANDBOX_LOG_FORMAT": "pretty" // json | pretty (default: json)
}
}
```
**Dev**: `debug` + `pretty`. **Production**: `info`/`warn` + `json`.
## Timeout Environment Overrides
Override default timeouts via environment variables:
```jsonc
{
"vars": {
"SANDBOX_INSTANCE_TIMEOUT_MS": "60000", // Override instanceGetTimeoutMS
"SANDBOX_PORT_TIMEOUT_MS": "120000" // Override portReadyTimeoutMS
}
}
```

View File

@@ -0,0 +1,194 @@
# Gotchas & Best Practices
## Common Errors
### "Container running indefinitely"
**Cause:** `keepAlive: true` without calling `destroy()`
**Solution:** Always call `destroy()` when done with keepAlive containers
```typescript
const sandbox = getSandbox(env.Sandbox, 'temp', { keepAlive: true });
try {
const result = await sandbox.exec('python script.py');
return result.stdout;
} finally {
await sandbox.destroy(); // REQUIRED to free resources
}
```
### "CONTAINER_NOT_READY"
**Cause:** Container still provisioning (first request or after sleep)
**Solution:** Retry after 2-3s
```typescript
async function execWithRetry(sandbox, cmd) {
for (let i = 0; i < 3; i++) {
try {
return await sandbox.exec(cmd);
} catch (e) {
if (e.code === 'CONTAINER_NOT_READY') {
await new Promise(r => setTimeout(r, 2000));
continue;
}
throw e;
}
}
}
```
### "Connection refused: container port not found"
**Cause:** Missing `EXPOSE` directive in Dockerfile
**Solution:** Add `EXPOSE <port>` to Dockerfile (only needed for `wrangler dev`, production auto-exposes)
### "Preview URLs not working"
**Cause:** Custom domain not configured, wildcard DNS missing, `normalizeId` not set, or `proxyToSandbox()` not called
**Solution:** Check:
1. Custom domain configured? (not `.workers.dev`)
2. Wildcard DNS set up? (`*.domain.com → worker.domain.com`)
3. `normalizeId: true` in getSandbox?
4. `proxyToSandbox()` called first in fetch?
### "Slow first request"
**Cause:** Cold start (container provisioning)
**Solution:**
- Use `sleepAfter` instead of creating new sandboxes
- Pre-warm with cron triggers
- Set `keepAlive: true` for critical sandboxes
### "File not persisting"
**Cause:** Files in `/tmp` or other ephemeral paths
**Solution:** Use `/workspace` for persistent files
### "Bucket mounting doesn't work locally"
**Cause:** Bucket mounting requires FUSE, not available in `wrangler dev`
**Solution:** Test bucket mounting in production only. Use mock data locally.
### "Different normalizeId = different sandbox"
**Cause:** Changing `normalizeId` option changes Durable Object ID
**Solution:** Set `normalizeId` consistently. `normalizeId: true` lowercases the ID.
```typescript
// These create DIFFERENT sandboxes:
getSandbox(env.Sandbox, 'MyApp'); // DO ID: hash('MyApp')
getSandbox(env.Sandbox, 'MyApp', { normalizeId: true }); // DO ID: hash('myapp')
```
### "Code context variables disappeared"
**Cause:** Container restart clears code context state
**Solution:** Code contexts are ephemeral. Recreate context after container sleep/wake.
## Performance Optimization
### Sandbox ID Strategy
```typescript
// ❌ BAD: New sandbox every time (slow)
const sandbox = getSandbox(env.Sandbox, `user-${Date.now()}`);
// ✅ GOOD: Reuse per user
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);
```
### Sleep & Traffic Config
```typescript
// Cost-optimized
getSandbox(env.Sandbox, 'id', { sleepAfter: '30m', keepAlive: false });
// Always-on (requires destroy())
getSandbox(env.Sandbox, 'id', { keepAlive: true });
```
```jsonc
// High traffic: increase max_instances
{ "containers": [{ "class_name": "Sandbox", "max_instances": 50 }] }
```
## Security Best Practices
### Sandbox Isolation
- Each sandbox = isolated container (filesystem, network, processes)
- Use unique sandbox IDs per tenant for multi-tenant apps
- Sandboxes cannot communicate directly
### Input Validation
```typescript
// ❌ DANGEROUS: Command injection
const result = await sandbox.exec(`python3 -c "${userCode}"`);
// ✅ SAFE: Write to file, execute file
await sandbox.writeFile('/workspace/user_code.py', userCode);
const result = await sandbox.exec('python3 /workspace/user_code.py');
```
### Resource Limits
```typescript
// Timeout long-running commands
const result = await sandbox.exec('python3 script.py', {
timeout: 30000 // 30 seconds
});
```
### Secrets Management
```typescript
// ❌ NEVER hardcode secrets
const token = 'ghp_abc123';
// ✅ Use environment secrets
const token = env.GITHUB_TOKEN;
// Pass to sandbox via exec env
const result = await sandbox.exec('git clone ...', {
env: { GIT_TOKEN: token }
});
```
### Preview URL Security
Preview URLs include auto-generated tokens:
```
https://8080-sandbox-abc123def456.yourdomain.com
```
Token changes on each expose operation, preventing unauthorized access.
## Limits
| Resource | Lite | Standard | Heavy |
|----------|------|----------|-------|
| RAM | 256MB | 512MB | 1GB |
| vCPU | 0.5 | 1 | 2 |
| Operation | Default Timeout | Override |
|-----------|----------------|----------|
| Container provisioning | 30s | `SANDBOX_INSTANCE_TIMEOUT_MS` |
| Port readiness | 90s | `SANDBOX_PORT_TIMEOUT_MS` |
| exec() | 120s | `timeout` option |
| sleepAfter | 10m | `sleepAfter` option |
**Performance**:
- **First deploy**: 2-3 min for container build
- **Cold start**: 2-3s when waking from sleep
- **Bucket mounting**: Production only (FUSE not in dev)
## Production Guide
See: https://developers.cloudflare.com/sandbox/guides/production-deployment/
## Resources
- [Official Docs](https://developers.cloudflare.com/sandbox/)
- [API Reference](https://developers.cloudflare.com/sandbox/api/)
- [Examples](https://github.com/cloudflare/sandbox-sdk/tree/main/examples)
- [npm Package](https://www.npmjs.com/package/@cloudflare/sandbox)
- [Discord Support](https://discord.cloudflare.com)

View File

@@ -0,0 +1,201 @@
# Common Patterns
## AI Code Execution with Code Context
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { code, variables } = await request.json();
const sandbox = getSandbox(env.Sandbox, 'ai-agent');
// Create context with persistent variables
const ctx = await sandbox.createCodeContext({
language: 'python',
variables: variables || {}
});
// Execute with rich outputs (text, images, HTML)
const result = await ctx.runCode(code);
return Response.json({
outputs: result.outputs, // [{ type: 'text'|'image'|'html', content }]
error: result.error,
success: !result.error
});
}
};
```
## Interactive Dev Environment
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
const sandbox = getSandbox(env.Sandbox, 'ide', { normalizeId: true });
if (request.url.endsWith('/start')) {
await sandbox.exec('curl -fsSL https://code-server.dev/install.sh | sh');
await sandbox.startProcess('code-server --bind-addr 0.0.0.0:8080', {
processId: 'vscode'
});
const exposed = await sandbox.exposePort(8080);
return Response.json({ url: exposed.url });
}
return new Response('Try /start');
}
};
```
## WebSocket Real-Time Service
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
const sandbox = getSandbox(env.Sandbox, 'realtime-service');
return await sandbox.wsConnect(request, 8080);
}
// Non-WebSocket: expose preview URL
const sandbox = getSandbox(env.Sandbox, 'realtime-service');
const { url } = await sandbox.exposePort(8080, {
hostname: new URL(request.url).hostname
});
return Response.json({ wsUrl: url.replace('https', 'wss') });
}
};
```
**Dockerfile**:
```dockerfile
FROM docker.io/cloudflare/sandbox:latest
RUN npm install -g ws
EXPOSE 8080
```
## Process Readiness Pattern
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, 'app-server');
// Start server
const process = await sandbox.startProcess(
'node server.js',
{ processId: 'server' }
);
// Wait for server to be ready
await process.waitForPort(8080); // Wait for port listening
// Now safe to expose
const { url } = await sandbox.exposePort(8080);
return Response.json({ url });
}
};
```
## Persistent Data with Bucket Mounting
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, 'data-processor');
// Mount R2 bucket (production only)
await sandbox.mountBucket(env.DATA_BUCKET, '/data', {
readOnly: false
});
// Process files in bucket
const result = await sandbox.exec('python3 /workspace/process.py', {
env: { DATA_DIR: '/data/input' }
});
// Results written to /data/output are persisted in R2
return Response.json({ success: result.success });
}
};
```
## CI/CD Pipeline
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { repo, branch } = await request.json();
const sandbox = getSandbox(env.Sandbox, `ci-${repo}-${Date.now()}`);
await sandbox.exec(`git clone -b ${branch} ${repo} /workspace/repo`);
const install = await sandbox.exec('npm install', {
cwd: '/workspace/repo',
stream: true,
onOutput: (stream, data) => console.log(data)
});
if (!install.success) {
return Response.json({ success: false, error: 'Install failed' });
}
const test = await sandbox.exec('npm test', { cwd: '/workspace/repo' });
return Response.json({
success: test.success,
output: test.stdout,
exitCode: test.exitCode
});
}
};
```
## Multi-Tenant Pattern
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const userId = request.headers.get('X-User-ID');
const sandbox = getSandbox(env.Sandbox, 'multi-tenant');
// Each user gets isolated session
let session;
try {
session = await sandbox.getSession(userId);
} catch {
session = await sandbox.createSession({
id: userId,
cwd: `/workspace/users/${userId}`,
env: { USER_ID: userId }
});
}
const code = await request.text();
const result = await session.exec(`python3 -c "${code}"`);
return Response.json({ output: result.stdout });
}
};
```
## Git Operations
```typescript
// Clone repo
await sandbox.exec('git clone https://github.com/user/repo.git /workspace/repo');
// Authenticated (use env secrets)
await sandbox.exec(`git clone https://${env.GITHUB_TOKEN}@github.com/user/repo.git`);
```