mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 00:04:54 +00:00
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>
This commit is contained in:
parent
cc37ed0458
commit
33ace91cfd
10 changed files with 1021 additions and 34 deletions
189
docs/deploy/cloudflare.mdx
Normal file
189
docs/deploy/cloudflare.mdx
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { getSandbox, proxyToSandbox } from "@cloudflare/sandbox";
|
||||||
|
export { Sandbox } from "@cloudflare/sandbox";
|
||||||
|
|
||||||
|
type Env = {
|
||||||
|
Sandbox: DurableObjectNamespace<Sandbox>;
|
||||||
|
ANTHROPIC_API_KEY?: string;
|
||||||
|
OPENAI_API_KEY?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async fetch(request: Request, env: Env): Promise<Response> {
|
||||||
|
// Proxy requests to exposed ports first
|
||||||
|
const proxyResponse = await proxyToSandbox(request, env);
|
||||||
|
if (proxyResponse) return proxyResponse;
|
||||||
|
|
||||||
|
const { hostname } = new URL(request.url);
|
||||||
|
const sandbox = getSandbox(env.Sandbox, "sandbox-agent");
|
||||||
|
|
||||||
|
// Install sandbox-agent
|
||||||
|
await sandbox.exec(
|
||||||
|
"curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Install agents
|
||||||
|
await sandbox.exec("sandbox-agent install-agent claude");
|
||||||
|
await sandbox.exec("sandbox-agent install-agent codex");
|
||||||
|
|
||||||
|
// 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 as background process
|
||||||
|
await sandbox.startProcess(
|
||||||
|
"sandbox-agent server --no-token --host 0.0.0.0 --port 8000"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for server to start
|
||||||
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
// Expose the port with a preview URL
|
||||||
|
const exposed = await sandbox.exposePort(8000, { hostname });
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
endpoint: exposed.url,
|
||||||
|
message: "sandbox-agent server is running",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connect from Client
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SandboxAgent } from "sandbox-agent";
|
||||||
|
|
||||||
|
// Get the endpoint from the Worker
|
||||||
|
const { endpoint } = await fetch("http://localhost:8787").then((r) => r.json());
|
||||||
|
|
||||||
|
// Connect to sandbox-agent
|
||||||
|
const client = await SandboxAgent.connect({ baseUrl: endpoint });
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
permissionMode: "default",
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.postMessage("my-session", {
|
||||||
|
message: "Summarize this repository",
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const event of client.streamEvents("my-session")) {
|
||||||
|
console.log(event.type, event.data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Update `wrangler.jsonc` to add environment variables:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"vars": {
|
||||||
|
"ANTHROPIC_API_KEY": "your-api-key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use `.dev.vars` for local development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "ANTHROPIC_API_KEY=your-api-key" > .dev.vars
|
||||||
|
```
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
The `.dev.vars` file is automatically gitignored and only used during local development with `npm run dev`.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
For production, preview URLs require a custom domain with wildcard DNS routing.
|
||||||
|
|
||||||
|
See [Cloudflare Production Deployment](https://developers.cloudflare.com/sandbox/guides/production-deployment/) for setup instructions.
|
||||||
|
|
||||||
|
## Faster Cold Starts
|
||||||
|
|
||||||
|
To speed up cold starts, you can:
|
||||||
|
|
||||||
|
1. Create a custom Dockerfile with sandbox-agent pre-installed
|
||||||
|
2. Pre-install agents in the Docker image
|
||||||
|
|
||||||
|
Example `Dockerfile`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM docker.io/cloudflare/sandbox:latest
|
||||||
|
|
||||||
|
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh && \
|
||||||
|
sandbox-agent install-agent claude && \
|
||||||
|
sandbox-agent install-agent codex
|
||||||
|
```
|
||||||
|
|
||||||
|
Then update `wrangler.jsonc`:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"containers": {
|
||||||
|
"sandbox": {
|
||||||
|
"image": "./Dockerfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -15,6 +15,9 @@ icon: "server"
|
||||||
<Card title="Vercel" icon="triangle" href="/deploy/vercel">
|
<Card title="Vercel" icon="triangle" href="/deploy/vercel">
|
||||||
Deploy inside a Vercel Sandbox with port forwarding.
|
Deploy inside a Vercel Sandbox with port forwarding.
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card title="Cloudflare" icon="cloud" href="/deploy/cloudflare">
|
||||||
|
Deploy inside a Cloudflare Sandbox with port exposure.
|
||||||
|
</Card>
|
||||||
<Card title="Daytona" icon="cloud" href="/deploy/daytona">
|
<Card title="Daytona" icon="cloud" href="/deploy/daytona">
|
||||||
Run in a Daytona workspace with port forwarding.
|
Run in a Daytona workspace with port forwarding.
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
"deploy/local",
|
"deploy/local",
|
||||||
"deploy/e2b",
|
"deploy/e2b",
|
||||||
"deploy/vercel",
|
"deploy/vercel",
|
||||||
|
"deploy/cloudflare",
|
||||||
"deploy/daytona",
|
"deploy/daytona",
|
||||||
"deploy/docker"
|
"deploy/docker"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
3
examples/cloudflare/.gitignore
vendored
Normal file
3
examples/cloudflare/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
.wrangler/
|
||||||
|
.dev.vars
|
||||||
46
examples/cloudflare/README.md
Normal file
46
examples/cloudflare/README.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Cloudflare Sandbox Agent Example
|
||||||
|
|
||||||
|
Deploy sandbox-agent 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
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create `.dev.vars` with your API keys:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "ANTHROPIC_API_KEY=your-api-key" > .dev.vars
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Start the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Test the endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8787
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Production preview URLs require a custom domain with wildcard DNS routing.
|
||||||
|
See [Cloudflare Production Deployment](https://developers.cloudflare.com/sandbox/guides/production-deployment/) for details.
|
||||||
20
examples/cloudflare/package.json
Normal file
20
examples/cloudflare/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "@sandbox-agent/example-cloudflare",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "wrangler dev",
|
||||||
|
"deploy": "wrangler deploy",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@cloudflare/sandbox": "latest"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "latest",
|
||||||
|
"@types/node": "latest",
|
||||||
|
"typescript": "latest",
|
||||||
|
"vitest": "^3.0.0",
|
||||||
|
"wrangler": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
52
examples/cloudflare/src/cloudflare.ts
Normal file
52
examples/cloudflare/src/cloudflare.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { getSandbox, proxyToSandbox, type Sandbox } from "@cloudflare/sandbox";
|
||||||
|
export { Sandbox } from "@cloudflare/sandbox";
|
||||||
|
|
||||||
|
type Env = {
|
||||||
|
Sandbox: DurableObjectNamespace<Sandbox>;
|
||||||
|
ANTHROPIC_API_KEY?: string;
|
||||||
|
OPENAI_API_KEY?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async fetch(request: Request, env: Env): Promise<Response> {
|
||||||
|
// Proxy requests to exposed ports first
|
||||||
|
const proxyResponse = await proxyToSandbox(request, env);
|
||||||
|
if (proxyResponse) return proxyResponse;
|
||||||
|
|
||||||
|
const { hostname } = new URL(request.url);
|
||||||
|
const sandbox = getSandbox(env.Sandbox, "sandbox-agent");
|
||||||
|
|
||||||
|
console.log("Installing sandbox-agent...");
|
||||||
|
await sandbox.exec(
|
||||||
|
"curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Installing agents...");
|
||||||
|
await sandbox.exec("sandbox-agent install-agent claude");
|
||||||
|
await sandbox.exec("sandbox-agent install-agent codex");
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
console.log("Starting sandbox-agent server...");
|
||||||
|
await sandbox.startProcess(
|
||||||
|
"sandbox-agent server --no-token --host 0.0.0.0 --port 8000"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for server to start
|
||||||
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
// Expose the port with a preview URL
|
||||||
|
const exposed = await sandbox.exposePort(8000, { hostname });
|
||||||
|
|
||||||
|
console.log("Server accessible at:", exposed.url);
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
endpoint: exposed.url,
|
||||||
|
message: "sandbox-agent server is running",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
15
examples/cloudflare/tsconfig.json
Normal file
15
examples/cloudflare/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": ["esnext"],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"types": ["@cloudflare/workers-types"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
29
examples/cloudflare/wrangler.jsonc
Normal file
29
examples/cloudflare/wrangler.jsonc
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "sandbox-agent-cloudflare",
|
||||||
|
"main": "src/cloudflare.ts",
|
||||||
|
"compatibility_date": "2025-01-01",
|
||||||
|
"compatibility_flags": ["nodejs_compat"],
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"class_name": "Sandbox",
|
||||||
|
"image": "docker.io/cloudflare/sandbox:latest",
|
||||||
|
"instance_type": "lite",
|
||||||
|
"max_instances": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"durable_objects": {
|
||||||
|
"bindings": [
|
||||||
|
{
|
||||||
|
"class_name": "Sandbox",
|
||||||
|
"name": "Sandbox"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"migrations": [
|
||||||
|
{
|
||||||
|
"new_sqlite_classes": ["Sandbox"],
|
||||||
|
"tag": "v1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
697
pnpm-lock.yaml
generated
697
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue