---
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.
