Skip to content

Commit 70e98b6

Browse files
committed
Fix broken sandbox code examples & documentation
Fixed critical runtime errors in code examples: missing exports, incorrect API signatures, and missing required parameters. Added documentation for custom startup scripts and local development warnings for port exposure requirements.
1 parent 18168a9 commit 70e98b6

File tree

13 files changed

+319
-197
lines changed

13 files changed

+319
-197
lines changed

src/content/docs/sandbox/api/ports.mdx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,42 @@ Expose services running in your sandbox via public preview URLs. See [Preview UR
2020
Expose a port and get a preview URL.
2121

2222
```ts
23-
const response = await sandbox.exposePort(port: number, options?: ExposePortOptions): Promise<ExposePortResponse>
23+
const response = await sandbox.exposePort(port: number, options: ExposePortOptions): Promise<ExposePortResponse>
2424
```
2525

2626
**Parameters**:
2727

2828
- `port` - Port number to expose (1024-65535)
29-
- `options` (optional):
30-
- `name` - Friendly name for the port
29+
- `options`:
30+
- `hostname` - Your Worker's domain name (e.g., `'example.com'`). Required to construct preview URLs with wildcard subdomains like `https://8080-sandbox-abc123.example.com`. Cannot be a `.workers.dev` domain as it doesn't support wildcard DNS patterns.
31+
- `name` - Friendly name for the port (optional)
3132

3233
**Returns**: `Promise<ExposePortResponse>` with `port`, `exposedAt` (preview URL), `name`
3334

3435
<TypeScriptExample>
3536
```
37+
// Extract hostname from request
38+
const { hostname } = new URL(request.url);
39+
3640
await sandbox.startProcess('python -m http.server 8000');
37-
const exposed = await sandbox.exposePort(8000);
41+
const exposed = await sandbox.exposePort(8000, { hostname });
3842
3943
console.log('Available at:', exposed.exposedAt);
40-
// https://8000-abc123.example.com
44+
// https://8000-abc123.yourdomain.com
4145
4246
// Multiple services with names
4347
await sandbox.startProcess('node api.js');
44-
const api = await sandbox.exposePort(3000, { name: 'api' });
48+
const api = await sandbox.exposePort(3000, { hostname, name: 'api' });
4549
4650
await sandbox.startProcess('npm run dev');
47-
const frontend = await sandbox.exposePort(5173, { name: 'frontend' });
51+
const frontend = await sandbox.exposePort(5173, { hostname, name: 'frontend' });
4852
```
4953
</TypeScriptExample>
5054

55+
:::note[Local development]
56+
When using `wrangler dev`, you must add `EXPOSE` directives to your Dockerfile for each port. See [Expose Services guide](/sandbox/guides/expose-services/#local-development) for details.
57+
:::
58+
5159
### `unexposePort()`
5260

5361
Remove an exposed port and close its preview URL.
@@ -111,6 +119,8 @@ const response = await sandbox.wsConnect(request: Request, port: number): Promis
111119
```ts
112120
import { getSandbox } from "@cloudflare/sandbox";
113121

122+
export { Sandbox } from "@cloudflare/sandbox";
123+
114124
export default {
115125
async fetch(request: Request, env: Env): Promise<Response> {
116126
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {

src/content/docs/sandbox/concepts/containers.mdx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,16 @@ npm install express
6666

6767
**Inbound connections** require port exposure:
6868
```typescript
69+
const { hostname } = new URL(request.url);
6970
await sandbox.startProcess('python -m http.server 8000');
70-
const exposed = await sandbox.exposePort(8000);
71+
const exposed = await sandbox.exposePort(8000, { hostname });
7172
console.log(exposed.exposedAt); // Public URL
7273
```
7374

75+
:::note[Local development]
76+
When using `wrangler dev`, you must add `EXPOSE` directives to your Dockerfile for each port. See [Local development with ports](/sandbox/guides/expose-services/#local-development).
77+
:::
78+
7479
**Localhost** works within sandbox:
7580
```bash
7681
redis-server & # Start server

src/content/docs/sandbox/concepts/preview-urls.mdx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,23 @@ Preview URLs work in local development without configuration. For production, yo
1212
Preview URLs provide public HTTPS access to services running inside sandboxes. When you expose a port, you get a unique URL that proxies requests to your service.
1313

1414
```typescript
15+
// Extract hostname from request
16+
const { hostname } = new URL(request.url);
17+
1518
await sandbox.startProcess("python -m http.server 8000");
16-
const exposed = await sandbox.exposePort(8000);
19+
const exposed = await sandbox.exposePort(8000, { hostname });
1720

1821
console.log(exposed.exposedAt);
19-
// Production: https://8000-abc123.example.com
22+
// Production: https://8000-abc123.yourdomain.com
2023
// Local dev: http://localhost:8787/...
2124
```
2225

2326
## URL Format
2427

2528
**Production**: `https://{port}-{sandbox-id}.yourdomain.com`
2629

27-
- Port 8080: `https://8080-abc123.example.com`
28-
- Port 3000: `https://3000-abc123.example.com`
30+
- Port 8080: `https://8080-abc123.yourdomain.com`
31+
- Port 3000: `https://3000-abc123.yourdomain.com`
2932

3033
**Local development**: `http://localhost:8787/...`
3134

@@ -38,6 +41,8 @@ You must call `proxyToSandbox()` first in your Worker's fetch handler to route p
3841
```typescript
3942
import { proxyToSandbox, getSandbox } from "@cloudflare/sandbox";
4043

44+
export { Sandbox } from "@cloudflare/sandbox";
45+
4146
export default {
4247
async fetch(request, env) {
4348
// Handle preview URL routing first
@@ -57,15 +62,18 @@ Requests flow: Browser → Your Worker → Durable Object (sandbox) → Your Ser
5762
Expose multiple services simultaneously:
5863

5964
```typescript
65+
// Extract hostname from request
66+
const { hostname } = new URL(request.url);
67+
6068
await sandbox.startProcess("node api.js"); // Port 3000
6169
await sandbox.startProcess("node admin.js"); // Port 3001
6270

63-
const api = await sandbox.exposePort(3000, { name: "api" });
64-
const admin = await sandbox.exposePort(3001, { name: "admin" });
71+
const api = await sandbox.exposePort(3000, { hostname, name: "api" });
72+
const admin = await sandbox.exposePort(3001, { hostname, name: "admin" });
6573

6674
// Each gets its own URL:
67-
// https://3000-abc123.example.com
68-
// https://3001-abc123.example.com
75+
// https://3000-abc123.yourdomain.com
76+
// https://3001-abc123.yourdomain.com
6977
```
7078

7179
## What Works
@@ -88,17 +96,21 @@ const admin = await sandbox.exposePort(3001, { name: "admin" });
8896
Preview URLs support WebSocket connections. When a WebSocket upgrade request hits an exposed port, the routing layer automatically handles the connection handshake.
8997

9098
```typescript
99+
// Extract hostname from request
100+
const { hostname } = new URL(request.url);
101+
91102
// Start a WebSocket server
92103
await sandbox.startProcess("bun run ws-server.ts 8080");
93-
const { exposedAt } = await sandbox.exposePort(8080);
104+
const { exposedAt } = await sandbox.exposePort(8080, { hostname });
94105

95106
// Clients connect using WebSocket protocol
96-
// Browser: new WebSocket('wss://8080-abc123.example.com')
107+
// Browser: new WebSocket('wss://8080-abc123.yourdomain.com')
97108

98109
// Your Worker routes automatically
99110
export default {
100111
async fetch(request, env) {
101-
return proxyToSandbox(request, env.Sandbox, "sandbox-id");
112+
const proxyResponse = await proxyToSandbox(request, env);
113+
if (proxyResponse) return proxyResponse;
102114
},
103115
};
104116
```
@@ -113,7 +125,7 @@ Preview URLs are publicly accessible by default, but require a valid access toke
113125

114126
**Built-in security**:
115127

116-
- **Token-based access** - Each exposed port gets a unique token in the URL (for example, `https://8080-sandbox-abc123token.example.com`)
128+
- **Token-based access** - Each exposed port gets a unique token in the URL (for example, `https://8080-sandbox-abc123token.yourdomain.com`)
117129
- **HTTPS in production** - All traffic is encrypted with automatic TLS
118130
- **Unpredictable URLs** - Tokens are randomly generated and difficult to guess
119131

src/content/docs/sandbox/concepts/security.mdx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,13 @@ export default {
8181

8282
### Preview URLs
8383

84-
Preview URLs are public. Add authentication in your service:
84+
Preview URLs include randomly generated tokens. Anyone with the URL can access the service.
85+
86+
To revoke access, unexpose the port:
87+
88+
```typescript
89+
await sandbox.unexposePort(8080);
90+
```
8591

8692
```python
8793
from flask import Flask, request, abort

src/content/docs/sandbox/concepts/sessions.mdx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,6 @@ await session1.startProcess('node server.js');
122122
await session2.listProcesses(); // Sees the server
123123
```
124124

125-
**Ports**:
126-
```typescript
127-
await session1.exposePort(3000);
128-
await session2.getExposedPorts(); // Sees port 3000
129-
```
130-
131125
## When to use sessions
132126

133127
**Use sessions when**:

src/content/docs/sandbox/configuration/dockerfile.mdx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,43 @@ Update `wrangler.jsonc` to reference your Dockerfile:
6666

6767
When you run `wrangler dev` or `wrangler deploy`, Wrangler automatically builds your Docker image and pushes it to Cloudflare's container registry. You don't need to manually build or publish images.
6868

69+
## Custom startup scripts
70+
71+
Run services automatically when the container starts by creating a custom startup script:
72+
73+
```dockerfile title="Dockerfile"
74+
FROM docker.io/cloudflare/sandbox:0.3.3
75+
76+
COPY my-app.js /workspace/my-app.js
77+
COPY startup.sh /workspace/startup.sh
78+
CMD ["/workspace/startup.sh"]
79+
```
80+
81+
```bash title="startup.sh"
82+
#!/bin/bash
83+
84+
# Start your services in the background
85+
node /workspace/my-app.js &
86+
87+
# Must end with this command
88+
exec bun /container-server/dist/index.js
89+
```
90+
91+
Your startup script must end with `exec bun /container-server/dist/index.js` to start the SDK's control plane.
92+
93+
### Multiple services
94+
95+
```bash title="startup.sh"
96+
#!/bin/bash
97+
98+
redis-server --daemonize yes
99+
until redis-cli ping; do sleep 1; done
100+
101+
node /workspace/api-server.js &
102+
103+
exec bun /container-server/dist/index.js
104+
```
105+
69106
## Related resources
70107

71108
- [Image Management](/containers/platform-details/image-management/) - Building and pushing images to Cloudflare\'s registry

src/content/docs/sandbox/guides/background-processes.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ By default, containers automatically shut down after 10 minutes of inactivity. F
167167
```ts
168168
import { getSandbox, parseSSEStream, type LogEvent } from '@cloudflare/sandbox';
169169

170+
export { Sandbox } from '@cloudflare/sandbox';
171+
170172
export default {
171173
async fetch(request: Request, env: Env): Promise<Response> {
172174
// Enable keepAlive for long-running processes

src/content/docs/sandbox/guides/code-execution.mdx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ const context = await sandbox.createCodeContext({
6363
});
6464
6565
// Execute code
66-
const result = await sandbox.runCode(context.id, `
66+
const result = await sandbox.runCode(`
6767
print("Hello from Code Interpreter!")
6868
result = 2 + 2
6969
print(f"2 + 2 = {result}")
70-
`);
70+
`, { context: context.id });
7171
7272
console.log('Output:', result.output);
7373
console.log('Success:', result.success);
@@ -85,19 +85,19 @@ const context = await sandbox.createCodeContext({
8585
});
8686
8787
// First execution - import and define variables
88-
await sandbox.runCode(context.id, `
88+
await sandbox.runCode(`
8989
import pandas as pd
9090
import numpy as np
9191
9292
data = [1, 2, 3, 4, 5]
9393
print("Data initialized")
94-
`);
94+
`, { context: context.id });
9595
9696
// Second execution - use previously defined variables
97-
const result = await sandbox.runCode(context.id, `
97+
const result = await sandbox.runCode(`
9898
mean = np.mean(data)
9999
print(f"Mean: {mean}")
100-
`);
100+
`, { context: context.id });
101101
102102
console.log(result.output); // "Mean: 3.0"
103103
```
@@ -113,13 +113,13 @@ The code interpreter returns multiple output formats:
113113

114114
<TypeScriptExample>
115115
```
116-
const result = await sandbox.runCode(context.id, `
116+
const result = await sandbox.runCode(`
117117
import matplotlib.pyplot as plt
118118
119119
plt.plot([1, 2, 3], [1, 4, 9])
120120
plt.title('Simple Chart')
121121
plt.show()
122-
`);
122+
`, { context: context.id });
123123
124124
// Check available formats
125125
console.log('Formats:', result.formats); // ['text', 'png']
@@ -157,7 +157,6 @@ const context = await sandbox.createCodeContext({
157157
});
158158
159159
const result = await sandbox.runCode(
160-
context.id,
161160
`
162161
import time
163162
@@ -168,6 +167,7 @@ for i in range(10):
168167
print("Done!")
169168
`,
170169
{
170+
context: context.id,
171171
stream: true,
172172
onOutput: (data) => {
173173
console.log('Output:', data);
@@ -212,7 +212,7 @@ const code = content[0].text;
212212
213213
// 2. Execute in sandbox
214214
const context = await sandbox.createCodeContext({ language: 'python' });
215-
const result = await sandbox.runCode(context.id, code);
215+
const result = await sandbox.runCode(code, { context: context.id });
216216
217217
console.log('Generated code:', code);
218218
console.log('Output:', result.output);

0 commit comments

Comments
 (0)