Chapter 04 · Cloudflare Deploy
Subchapter 4.185
references/sandbox/gotchas.mdMarkdown5 KBView on GitHub
Cause: keepAlive: true without calling destroy()
Solution: Always call destroy() when done with keepAlive containers
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
}Cause: Container still provisioning (first request or after sleep) Solution: Retry after 2-3s
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;
}
}
}Cause: Missing EXPOSE directive in Dockerfile
Solution: Add EXPOSE <port> to Dockerfile (only needed for wrangler dev, production auto-exposes)
Cause: Custom domain not configured, wildcard DNS missing, normalizeId not set, or proxyToSandbox() not called
Solution: Check:
.workers.dev)*.domain.com → worker.domain.com)normalizeId: true in getSandbox?proxyToSandbox() called first in fetch?Cause: Cold start (container provisioning) Solution:
sleepAfter instead of creating new sandboxeskeepAlive: true for critical sandboxesCause: Files in /tmp or other ephemeral paths
Solution: Use /workspace for persistent files
Cause: Bucket mounting requires FUSE, not available in wrangler dev
Solution: Test bucket mounting in production only. Use mock data locally.
Cause: Changing normalizeId option changes Durable Object ID
Solution: Set normalizeId consistently. normalizeId: true lowercases the ID.
// These create DIFFERENT sandboxes:
getSandbox(env.Sandbox, 'MyApp'); // DO ID: hash('MyApp')
getSandbox(env.Sandbox, 'MyApp', { normalizeId: true }); // DO ID: hash('myapp')Cause: Container restart clears code context state Solution: Code contexts are ephemeral. Recreate context after container sleep/wake.
// ❌ 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}`);// Cost-optimized
getSandbox(env.Sandbox, 'id', { sleepAfter: '30m', keepAlive: false });
// Always-on (requires destroy())
getSandbox(env.Sandbox, 'id', { keepAlive: true });// High traffic: increase max_instances
{ "containers": [{ "class_name": "Sandbox", "max_instances": 50 }] }// ❌ 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');// Timeout long-running commands
const result = await sandbox.exec('python3 script.py', {
timeout: 30000 // 30 seconds
});// ❌ 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 URLs include auto-generated tokens:
https://8080-sandbox-abc123def456.yourdomain.comToken changes on each expose operation, preventing unauthorized access.
| 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: