mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-20 06:11:27 -07:00
update skills
This commit is contained in:
@@ -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
|
||||
198
.agents/skills/cloudflare-deploy/references/sandbox/api.md
Normal file
198
.agents/skills/cloudflare-deploy/references/sandbox/api.md
Normal 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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
194
.agents/skills/cloudflare-deploy/references/sandbox/gotchas.md
Normal file
194
.agents/skills/cloudflare-deploy/references/sandbox/gotchas.md
Normal 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)
|
||||
201
.agents/skills/cloudflare-deploy/references/sandbox/patterns.md
Normal file
201
.agents/skills/cloudflare-deploy/references/sandbox/patterns.md
Normal 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`);
|
||||
```
|
||||
Reference in New Issue
Block a user