sandbox-agent/docs/deploy/cloudflare.mdx
Jordan Coeyman cc5a9e0d73
docs: add Cloudflare Sandbox SDK deployment guide (#57)
* docs: add Cloudflare Sandbox SDK deployment guide

- Add docs/deploy/cloudflare.mdx with full deployment guide
- Add examples/cloudflare/ with working Worker code
- Update docs navigation to include Cloudflare option
- Update deploy index page with Cloudflare card

The example shows how to run sandbox-agent inside a Cloudflare Sandbox
with exposed ports for API access.

Co-authored-by: Shelley <shelley@exe.dev>

* fix: guard server startup to avoid port conflicts

Add health check before starting sandbox-agent to prevent 'address already
in use' errors on subsequent requests. The isServerRunning() function probes
the health endpoint to determine if setup should be skipped.

Co-authored-by: Shelley <shelley@exe.dev>

* fix: default cloudflare/sandbox:0.7.0 (latest does not exist)

* feat(cloudflare): add React frontend and improve deployment docs

- Add React + Vite frontend for Cloudflare example with sandbox-agent SDK
- Update ensureRunning to poll health endpoint instead of fixed wait
- Fix SDK fetch binding issue (globalThis.fetch.bind)
- Update docs with .dev.vars format warning and container caching tip
- Use containerFetch proxy pattern for reliable local dev

---------

Co-authored-by: Shelley <shelley@exe.dev>
Co-authored-by: Nathan Flurry <git@nathanflurry.com>
Co-authored-by: Nathan Flurry <developer@nathanflurry.com>
2026-02-03 02:15:34 -08:00

251 lines
6 KiB
Text

---
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
<Note>
Cloudflare Sandbox SDK is in beta. See [Sandbox SDK docs](https://developers.cloudflare.com/sandbox/) for details.
</Note>
## 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
```
<Note>
The `EXPOSE 8000` directive is required for `wrangler dev` to proxy requests to the container. Port 3000 is reserved for the Cloudflare control plane.
</Note>
## 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<Sandbox>;
ANTHROPIC_API_KEY?: string;
OPENAI_API_KEY?: string;
};
const PORT = 8000;
/** Check if sandbox-agent is already running */
async function isServerRunning(sandbox: Sandbox): Promise<boolean> {
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<void> {
if (await isServerRunning(sandbox)) return;
// Set environment variables for agents
const envVars: Record<string, string> = {};
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<Response> {
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
```
<Warning>
Use plain `KEY=value` format in `.dev.vars`. Do not use `export KEY=value` - wrangler won't parse the bash syntax.
</Warning>
<Note>
The `.dev.vars` file is automatically gitignored and only used during local development with `npm run dev`.
</Note>
For production, set secrets via wrangler:
```bash
wrangler secret put ANTHROPIC_API_KEY
```
## Local Development
Start the development server:
```bash
npm run dev
```
<Note>
First run builds the Docker container (2-3 minutes). Subsequent runs are much faster.
</Note>
Test with curl:
```bash
curl http://localhost:8787/sandbox/demo/v1/health
```
<Tip>
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
```
</Tip>
## 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.