# 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 ` 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)