--- title: "Cloudflare" description: "Deploy the daemon inside a Cloudflare Sandbox." --- ## Prerequisites - Cloudflare account with Workers Paid plan - Docker running locally for `wrangler dev` - `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` for the coding agents Cloudflare Sandbox SDK is in beta. See [Sandbox SDK docs](https://developers.cloudflare.com/sandbox/) for details. ## Quick Start Create a new Sandbox SDK project: ```bash npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/examples/minimal cd my-sandbox ``` ## Dockerfile Create a `Dockerfile` with sandbox-agent and agents pre-installed: ```dockerfile FROM cloudflare/sandbox:0.7.0 # Install sandbox-agent RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh # Pre-install agents RUN sandbox-agent install-agent claude && \ sandbox-agent install-agent codex # Required for local development with wrangler dev EXPOSE 8000 ``` The `EXPOSE 8000` directive is required for `wrangler dev` to proxy requests to the container. Port 3000 is reserved for the Cloudflare control plane. ## Wrangler Configuration Update `wrangler.jsonc` to use your Dockerfile: ```jsonc { "name": "my-sandbox-agent", "main": "src/index.ts", "compatibility_date": "2025-01-01", "compatibility_flags": ["nodejs_compat"], "containers": [ { "class_name": "Sandbox", "image": "./Dockerfile", "instance_type": "lite", "max_instances": 1 } ], "durable_objects": { "bindings": [ { "class_name": "Sandbox", "name": "Sandbox" } ] }, "migrations": [ { "new_sqlite_classes": ["Sandbox"], "tag": "v1" } ] } ``` ## TypeScript Example This example proxies requests to sandbox-agent via `containerFetch`, which works reliably in both local development and production: ```typescript import { getSandbox, type Sandbox } from "@cloudflare/sandbox"; export { Sandbox } from "@cloudflare/sandbox"; type Env = { Sandbox: DurableObjectNamespace; ANTHROPIC_API_KEY?: string; OPENAI_API_KEY?: string; }; const PORT = 8000; /** Check if sandbox-agent is already running */ async function isServerRunning(sandbox: Sandbox): Promise { try { const result = await sandbox.exec(`curl -sf http://localhost:${PORT}/v1/health`); return result.success; } catch { return false; } } /** Ensure sandbox-agent is running in the container */ async function ensureRunning(sandbox: Sandbox, env: Env): Promise { if (await isServerRunning(sandbox)) return; // Set environment variables for agents const envVars: Record = {}; if (env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = env.ANTHROPIC_API_KEY; if (env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = env.OPENAI_API_KEY; await sandbox.setEnvVars(envVars); // Start sandbox-agent server await sandbox.startProcess( `sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}` ); // Poll health endpoint until server is ready for (let i = 0; i < 30; i++) { if (await isServerRunning(sandbox)) return; await new Promise((r) => setTimeout(r, 200)); } } export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); // Proxy requests: /sandbox/:name/v1/... const match = url.pathname.match(/^\/sandbox\/([^/]+)(\/.*)?$/); if (match) { const [, name, path = "/"] = match; const sandbox = getSandbox(env.Sandbox, name); await ensureRunning(sandbox, env); // Proxy request to container return sandbox.containerFetch( new Request(`http://localhost${path}${url.search}`, request), PORT ); } return new Response("Not found", { status: 404 }); }, }; ``` ## Connect from Client ```typescript import { SandboxAgent } from "sandbox-agent"; // Connect via the proxy endpoint const client = await SandboxAgent.connect({ baseUrl: "http://localhost:8787/sandbox/my-sandbox", }); // Wait for server to be ready for (let i = 0; i < 30; i++) { try { await client.getHealth(); break; } catch { await new Promise((r) => setTimeout(r, 1000)); } } // Create a session and start coding await client.createSession("my-session", { agent: "claude" }); await client.postMessage("my-session", { message: "Summarize this repository", }); for await (const event of client.streamEvents("my-session")) { // Auto-approve permissions if (event.type === "permission.requested") { await client.replyPermission("my-session", event.data.permission_id, { reply: "once", }); } // Handle text output if (event.type === "item.delta" && event.data?.delta) { process.stdout.write(event.data.delta); } } ``` ## Environment Variables Use `.dev.vars` for local development: ```bash echo "ANTHROPIC_API_KEY=your-api-key" > .dev.vars ``` Use plain `KEY=value` format in `.dev.vars`. Do not use `export KEY=value` - wrangler won't parse the bash syntax. The `.dev.vars` file is automatically gitignored and only used during local development with `npm run dev`. For production, set secrets via wrangler: ```bash wrangler secret put ANTHROPIC_API_KEY ``` ## Local Development Start the development server: ```bash npm run dev ``` First run builds the Docker container (2-3 minutes). Subsequent runs are much faster. Test with curl: ```bash curl http://localhost:8787/sandbox/demo/v1/health ``` Containers cache environment variables. If you change `.dev.vars`, either use a new sandbox name or clear existing containers: ```bash docker ps -a | grep sandbox | awk '{print $1}' | xargs -r docker rm -f ``` ## Production Deployment Deploy to Cloudflare: ```bash wrangler deploy ``` For production with preview URLs (direct container access), you'll need a custom domain with wildcard DNS routing. See [Cloudflare Production Deployment](https://developers.cloudflare.com/sandbox/guides/production-deployment/) for setup instructions.