From 5e79e97673f455d86118fba79a003805495920bd Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Mon, 18 May 2026 12:12:27 +0100 Subject: [PATCH 1/6] [Sandboxes] Add new docs for tunnel support --- src/content/docs/sandbox/api/index.mdx | 6 + src/content/docs/sandbox/api/tunnels.mdx | 151 +++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 src/content/docs/sandbox/api/tunnels.mdx diff --git a/src/content/docs/sandbox/api/index.mdx b/src/content/docs/sandbox/api/index.mdx index 8a3d0dabac46bb5..411c6eab0594f69 100644 --- a/src/content/docs/sandbox/api/index.mdx +++ b/src/content/docs/sandbox/api/index.mdx @@ -52,6 +52,12 @@ The Sandbox SDK provides a comprehensive API for executing code, managing files, and APIs from the internet. + + Expose services on zero-config `*.trycloudflare.com` URLs via + `sandbox.tunnels.get(port)`. Best for quick development and `.workers.dev` + deployments. + + Mount S3-compatible buckets (R2, S3, GCS) as local filesystems for persistent data storage across sandbox lifecycles. diff --git a/src/content/docs/sandbox/api/tunnels.mdx b/src/content/docs/sandbox/api/tunnels.mdx new file mode 100644 index 000000000000000..7d45333180f49ed --- /dev/null +++ b/src/content/docs/sandbox/api/tunnels.mdx @@ -0,0 +1,151 @@ +--- +title: Tunnels +description: Expose sandbox services on zero-config *.trycloudflare.com URLs using the Sandbox SDK tunnels API. +pcx_content_type: reference +sidebar: + order: 6 +products: + - sandbox +--- + +import { TypeScriptExample } from "~/components"; + +The `sandbox.tunnels` namespace exposes a service running inside a sandbox on a `*.trycloudflare.com` URL. The SDK runs `cloudflared` inside the container and opens a persistent QUIC connection to Cloudflare's edge; no Cloudflare account, DNS record, or custom domain is required. + +:::note[When to use tunnels vs. `exposePort()`] +Use `sandbox.tunnels` for **quick development** and **deployments on `.workers.dev`**, where wildcard DNS is not available. For **production**, use [`exposePort()`](/sandbox/api/ports/) with a custom domain — quick tunnels are positioned by Cloudflare as a debug aid and do not carry an uptime guarantee. Production-grade named tunnels are planned for a future release. +::: + +## Requirements + +- **RPC transport.** Calling `sandbox.tunnels` on a route-based transport throws `"RPC transport required"`. See [Transport configuration](/sandbox/configuration/transport/). +- **glibc image variant.** The default, `python`, `opencode`, and `desktop` images ship `cloudflared`. The `musl`/Alpine variant does not — there is no upstream `cloudflared` build for musl at this time. + +## Methods + +### `tunnels.get()` + +Return a tunnel record for `port`. Idempotent: repeated calls for the same port return the same record. On a cache miss, the SDK spawns a fresh `cloudflared` process inside the container and persists the record in Durable Object storage. + +```ts +const tunnel = await sandbox.tunnels.get(port: number): Promise +``` + +**Parameters**: + +- `port` — Port number inside the sandbox to expose (1024-65535, excluding reserved ports). The service must already be listening on `0.0.0.0:` inside the container. + +**Returns**: `Promise` — the tunnel record. See [`TunnelInfo`](#tunnelinfo). + + +```ts +import { getSandbox } from "@cloudflare/sandbox"; + +export { Sandbox } from "@cloudflare/sandbox"; + +export default { + async fetch(request: Request, env: Env): Promise { + const sandbox = getSandbox(env.Sandbox, "my-sandbox"); + + await sandbox.startProcess("python -m http.server 8080"); + + const tunnel = await sandbox.tunnels.get(8080); + console.log(tunnel.url); + // → https://random-words-here.trycloudflare.com + + // Repeated calls for the same port return the same record. + const same = await sandbox.tunnels.get(8080); + console.log(same.url === tunnel.url); // true + + return Response.json({ url: tunnel.url }); + +}, +}; + +```` + + +### `tunnels.list()` + +Return every tunnel currently tracked for this sandbox. + +```ts +const tunnels = await sandbox.tunnels.list(): Promise +```` + +**Returns**: `Promise` — an array of [`TunnelInfo`](#tunnelinfo) records. Empty when no tunnels are active. + + +```ts +const tunnels = await sandbox.tunnels.list(); + +for (const tunnel of tunnels) { +console.log(`port ${tunnel.port} → ${tunnel.url}`); +} + +```` + + +### `tunnels.destroy()` + +Tear down a tunnel. Accepts either the port number or the `TunnelInfo` record returned by `get()`. Idempotent — destroying an unknown port resolves successfully. + +```ts +await sandbox.tunnels.destroy(portOrInfo: number | TunnelInfo): Promise +```` + +**Parameters**: + +- `portOrInfo` — Either the port number or the `TunnelInfo` record returned by [`get()`](#tunnelsget). + + +```ts +const tunnel = await sandbox.tunnels.get(8080); + +// Tear down by port number... +await sandbox.tunnels.destroy(8080); + +// ...or by the record. +await sandbox.tunnels.destroy(tunnel); + +```` + + +## Types + +### `TunnelInfo` + +| Field | Type | Description | +| ----------- | -------- | -------------------------------------------------------------------------------------------- | +| `id` | `string` | SDK-assigned identifier for the tunnel (for example, `quick-9f2c8a1d`). | +| `port` | `number` | Port number inside the sandbox that the tunnel proxies to. | +| `url` | `string` | Public URL — `https://.trycloudflare.com`. | +| `hostname` | `string` | Hostname component of `url` (`.trycloudflare.com`). | +| `createdAt` | `string` | ISO-8601 timestamp of when the tunnel was created. | + +```ts +interface TunnelInfo { + id: string; + port: number; + url: string; + hostname: string; + createdAt: string; +} +```` + +## Limitations + +- **URLs do not survive container restart.** Cloudflare assigns the hostname during `cloudflared`'s startup handshake, so every restart yields a new URL. The SDK clears its tunnel cache when the container starts, so the next `tunnels.get(port)` returns a fresh record. +- **No uptime guarantee.** Cloudflare positions `trycloudflare.com` as a debug aid, not a production target. Use [`exposePort()`](/sandbox/api/ports/) with a custom domain for production. +- **No Server-Sent Events.** The `trycloudflare.com` edge buffers `text/event-stream` responses, so SSE does not reach the client. WebSockets work normally. +- **No persistent hostname.** Every restart picks a new `.trycloudflare.com`. If you need a stable URL, use [`exposePort()`](/sandbox/api/ports/#exposeport) with a custom token. +- **Brief DNS warm-up.** The first request through a brand-new URL can take a couple of seconds while DNS propagates, even after `get()` resolves. +- **WARP / Zero Trust egress.** If your local machine runs Cloudflare WARP or another Zero Trust egress policy, outbound traffic to `api.trycloudflare.com` and the cloudflared edge can be blocked. When that happens, `tunnels.get()` hangs on the edge handshake and eventually times out. Disable WARP or add an egress exception for these destinations. +- **No musl/Alpine support.** The musl image variant does not include `cloudflared`. Use one of the glibc-based image variants (`default`, `python`, `opencode`, `desktop`). + +## Related resources + +- [Preview URLs concept](/sandbox/concepts/preview-urls/) — Worker-fronted preview URLs and how they differ from quick tunnels. +- [Ports API](/sandbox/api/ports/) — `exposePort()` and the Worker-fronted preview URL flow. +- [Expose services guide](/sandbox/guides/expose-services/) — End-to-end walkthrough for exposing services in production. +- [Transport configuration](/sandbox/configuration/transport/) — RPC vs. route-based transport. From 8d54e68acaca1ca662c7f786fc6d518d3aadde91 Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Mon, 18 May 2026 12:25:47 +0100 Subject: [PATCH 2/6] [Sandboxes] Add links back to the quick tunnels docs --- src/content/docs/sandbox/api/ports.mdx | 6 ++++++ src/content/docs/sandbox/concepts/index.mdx | 2 +- src/content/docs/sandbox/concepts/security.mdx | 10 ++++++++++ src/content/docs/sandbox/get-started.mdx | 5 +---- src/content/docs/sandbox/guides/expose-services.mdx | 5 +++++ .../docs/sandbox/guides/production-deployment.mdx | 5 ++++- .../docs/sandbox/guides/websocket-connections.mdx | 5 +++++ 7 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index 4b1b2a1424b67ac..45bc6195c3d1a39 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -14,6 +14,10 @@ import { TypeScriptExample } from "~/components"; Preview URLs require a custom domain with wildcard DNS routing in production. See [Production Deployment](/sandbox/guides/production-deployment/). ::: +:::note[Alternative: quick tunnels] +For quick development or `.workers.dev` deployments, consider [`sandbox.tunnels`](/sandbox/api/tunnels/), which returns `*.trycloudflare.com` URLs without DNS setup. `exposePort()` remains the recommended option for production. +::: + Expose services running in your sandbox via public preview URLs. See [Preview URLs concept](/sandbox/concepts/preview-urls/) for details. ## Module functions @@ -285,3 +289,5 @@ export default { - [Expose Services guide](/sandbox/guides/expose-services/) - Full workflow for starting services, exposing ports, and routing requests - [WebSocket Connections guide](/sandbox/guides/websocket-connections/) - WebSocket routing via preview URLs - [Commands API](/sandbox/api/commands/) - Start background processes +- [Tunnels API](/sandbox/api/tunnels/) - Zero-config `*.trycloudflare.com` URLs for quick development +``` diff --git a/src/content/docs/sandbox/concepts/index.mdx b/src/content/docs/sandbox/concepts/index.mdx index cb7291e6c1a03d2..586ad13822d0ac5 100644 --- a/src/content/docs/sandbox/concepts/index.mdx +++ b/src/content/docs/sandbox/concepts/index.mdx @@ -14,7 +14,7 @@ These pages explain how the Sandbox SDK works, why it's designed the way it is, - [Sandbox lifecycle](/sandbox/concepts/sandboxes/) - Understanding sandbox states and behavior - [Container runtime](/sandbox/concepts/containers/) - How code executes in isolated containers - [Session management](/sandbox/concepts/sessions/) - When and how to use sessions -- [Preview URLs](/sandbox/concepts/preview-urls/) - How service exposure works +- [Preview URLs](/sandbox/concepts/preview-urls/) - How to expose sandboxed services on the public internet. - [Security model](/sandbox/concepts/security/) - Isolation, validation, and safety mechanisms - [Terminal connections](/sandbox/concepts/terminal/) - How browser terminal connections work diff --git a/src/content/docs/sandbox/concepts/security.mdx b/src/content/docs/sandbox/concepts/security.mdx index 4319689e4e05b1c..5c15d8d4295ba00 100644 --- a/src/content/docs/sandbox/concepts/security.mdx +++ b/src/content/docs/sandbox/concepts/security.mdx @@ -92,6 +92,16 @@ To revoke access, unexpose the port: await sandbox.unexposePort(8080); ``` +### Quick tunnel URLs + +Quick tunnels (`sandbox.tunnels.get(port)`) return a `*.trycloudflare.com` URL with a random hostname assigned by Cloudflare — there is no separate access token. The hostname itself is the access control: anyone who knows the URL can reach the service. To revoke access, destroy the tunnel: + +```typescript +await sandbox.tunnels.destroy(8080); +``` + +URLs do not survive a container restart, so a restart effectively rotates the hostname. As with preview URLs, add application-level authentication for any sensitive service. See the [Tunnels API](/sandbox/api/tunnels/) for details. + ```python from flask import Flask, request, abort import os diff --git a/src/content/docs/sandbox/get-started.mdx b/src/content/docs/sandbox/get-started.mdx index de388a8cafd7cf8..1cf2ee8d1179b04 100644 --- a/src/content/docs/sandbox/get-started.mdx +++ b/src/content/docs/sandbox/get-started.mdx @@ -157,10 +157,6 @@ curl https://my-sandbox.YOUR_SUBDOMAIN.workers.dev/run Your sandbox is now deployed and can execute code in isolated containers. -:::note[Preview URLs require custom domain] -If you plan to expose ports from sandboxes (using `exposePort()` for preview URLs), you will need to set up a custom domain with wildcard DNS routing. The `.workers.dev` domain does not support the subdomain patterns required for preview URLs. See [Production Deployment](/sandbox/guides/production-deployment/) when you are ready to expose services. -::: - ## Understanding the configuration Your `wrangler.jsonc` connects three pieces together: @@ -210,5 +206,6 @@ Now that you have a working sandbox, explore more capabilities: - [Execute commands](/sandbox/guides/execute-commands/) - Run shell commands and stream output - [Manage files](/sandbox/guides/manage-files/) - Work with files and directories - [Expose services](/sandbox/guides/expose-services/) - Get public URLs for services running in your sandbox +- [Quick tunnels](/sandbox/api/tunnels/) - Zero-config `*.trycloudflare.com` URLs for development and `.workers.dev` deployments - [Production Deployment](/sandbox/guides/production-deployment/) - Set up custom domains for preview URLs - [API reference](/sandbox/api/) - Complete API documentation diff --git a/src/content/docs/sandbox/guides/expose-services.mdx b/src/content/docs/sandbox/guides/expose-services.mdx index 6f2b063ab65aea0..db3032671d42d7a 100644 --- a/src/content/docs/sandbox/guides/expose-services.mdx +++ b/src/content/docs/sandbox/guides/expose-services.mdx @@ -14,6 +14,10 @@ import { TypeScriptExample } from "~/components"; Preview URLs require a custom domain with wildcard DNS routing in production. See [Production Deployment](/sandbox/guides/production-deployment/) for setup instructions. ::: +:::note[Alternative: quick tunnels] +If you only need a public URL for development or a `.workers.dev` deployment, [`sandbox.tunnels`](/sandbox/api/tunnels/) returns a `*.trycloudflare.com` URL without DNS setup. For production, follow this guide and use `exposePort()` with a custom domain. +::: + This guide shows you how to expose services running in your sandbox to the internet via preview URLs. ## When to expose ports @@ -393,3 +397,4 @@ See [Sandbox options - normalizeId](/sandbox/configuration/sandbox-options/#norm - [Ports API reference](/sandbox/api/ports/) - Complete port exposure API - [Background processes guide](/sandbox/guides/background-processes/) - Managing services - [Execute commands guide](/sandbox/guides/execute-commands/) - Starting services +- [Tunnels API reference](/sandbox/api/tunnels/) - Quick `*.trycloudflare.com` URLs as an alternative to `exposePort()` diff --git a/src/content/docs/sandbox/guides/production-deployment.mdx b/src/content/docs/sandbox/guides/production-deployment.mdx index a527397f09ef99b..27c08a0c143f74a 100644 --- a/src/content/docs/sandbox/guides/production-deployment.mdx +++ b/src/content/docs/sandbox/guides/production-deployment.mdx @@ -11,7 +11,9 @@ products: import { WranglerConfig } from "~/components"; :::note[Only required for preview URLs] -Custom domain setup is ONLY needed if you use `exposePort()` to expose services from sandboxes. If your application does not expose ports, you can deploy to `.workers.dev` without this configuration. +This guide covers `exposePort()` in production. Custom domain setup is ONLY needed if you use `exposePort()` to expose services from sandboxes. If your application does not use `exposePort()`, you can deploy to `.workers.dev` without this configuration. + +For development or `.workers.dev` deployments that need public URLs, [`sandbox.tunnels`](/sandbox/api/tunnels/) is a zero-config alternative — we recommend `exposePort()` for production today; named tunnels for production are planned for a future release. ::: Deploy your Sandbox SDK application to production with preview URL support. Preview URLs require wildcard DNS routing because they generate unique subdomains for each exposed port: `https://8080-abc123.yourdomain.com`. @@ -108,5 +110,6 @@ For detailed troubleshooting, see the [Workers routing documentation](/workers/c - [Preview URLs](/sandbox/concepts/preview-urls/) - How preview URLs work - [Expose Services](/sandbox/guides/expose-services/) - Patterns for exposing ports +- [Tunnels API](/sandbox/api/tunnels/) - Zero-config `*.trycloudflare.com` URLs for development (not for production) - [Workers Routing](/workers/configuration/routing/) - Advanced routing configuration - [Cloudflare DNS](/dns/) - DNS management diff --git a/src/content/docs/sandbox/guides/websocket-connections.mdx b/src/content/docs/sandbox/guides/websocket-connections.mdx index f9ba738b14a25bb..f63858ba22b8a1a 100644 --- a/src/content/docs/sandbox/guides/websocket-connections.mdx +++ b/src/content/docs/sandbox/guides/websocket-connections.mdx @@ -138,6 +138,10 @@ export default { ``` +:::note[Alternative: quick tunnels] +Quick tunnels also handle WebSocket upgrades and do not require a custom domain, so they work on `.workers.dev`. Swap `sandbox.exposePort(8080, { hostname })` for `sandbox.tunnels.get(8080)` to get a `*.trycloudflare.com` URL. +::: + **Client connects to preview URL:** ```javascript @@ -246,4 +250,5 @@ Port exposure in Dockerfile is only required for local development. In productio - [Ports API reference](/sandbox/api/ports/) - Complete API documentation - [Preview URLs concept](/sandbox/concepts/preview-urls/) - How preview URLs work +- [Tunnels API](/sandbox/api/tunnels/) - Zero-config `*.trycloudflare.com` URLs for WebSocket services in development - [Background processes guide](/sandbox/guides/background-processes/) - Managing long-running services From 9798f0fa85c1e6dad0639c666229058b6f19967c Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Mon, 18 May 2026 12:39:52 +0100 Subject: [PATCH 3/6] [Sandboxes] Update the Preview URLs docs --- .../docs/sandbox/concepts/preview-urls.mdx | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/content/docs/sandbox/concepts/preview-urls.mdx b/src/content/docs/sandbox/concepts/preview-urls.mdx index 30bbf9f5c46d4fb..7fdf50c71ce90b4 100644 --- a/src/content/docs/sandbox/concepts/preview-urls.mdx +++ b/src/content/docs/sandbox/concepts/preview-urls.mdx @@ -8,6 +8,36 @@ products: - sandbox --- +# Quick deployment + +For quick preview deployments we recommend using [Cloudflare Tunnel](https://developers.cloudflare.com/tunnel/) to generate preview URLs to your web services. These work across local development, workers.dev and production usage. + +```ts +await sandbox.startProcess("python -m http.server 8000"); +const tunnel = await sandbox.tunnels.get(8000); +console.log(tunnel.url); +// https://acute-llama-dancing-roundly.trycloudflare.app + +// Request will be routed directly to the webserver running on the sandbox. +const req = await fetch(`${tunnel.url}/api/users`); // => GET http://localhost:8000/api/users +``` + +Cloudflare Tunnel support currently has the following limitations: + + - No control over generated URL. + - No authentication mechanism beyond randomly generated URL. + - Each URL uses an additional `cloudflared` process on the sandbox. + +:::note[Production requires custom domain] +We are working on production deployments, custom hostnames and authentication for Cloudflare Tunnel support. In the mean time we recommend using `exposePort()` and `proxyToSandbox()` documented below under [Production usage, stable URLs and custom domains](#). +::: + +See the [tunnels API reference](/sandbox/api/tunnels/) for the full API and feature set. + +# Production usage, stable URLs & custom domains + +For production use we recommend using the `exposePort()` API and routing traffic through your worker. + :::note[Production requires custom domain] Preview URLs work in local development without configuration. For production, you need a custom domain with wildcard DNS routing. See [Production Deployment](/sandbox/guides/production-deployment/). ::: @@ -53,20 +83,22 @@ URLs with auto-generated tokens change when you unexpose and re-expose a port. For production deployments or shared URLs, specify a custom token to maintain consistency across container restarts: ```typescript -const stable = await sandbox.exposePort(8000, { - hostname, - token: 'api_v1' +const stable = await sandbox.exposePort(8000, { + hostname, + token: "api_v1", }); // https://8000-sandbox-id-api_v1.yourdomain.com // Same URL every time ✓ ``` **Token requirements:** + - 1-16 characters long -- Lowercase letters (a-z), numbers (0-9), and underscores (_) only +- Lowercase letters (a-z), numbers (0-9), and underscores (\_) only - Must be unique within each sandbox **Use cases for custom tokens:** + - Production APIs with stable endpoints - Sharing demo URLs with external users - Documentation with consistent examples @@ -80,7 +112,7 @@ Preview URLs extract the sandbox ID from the hostname to route requests. Since h ```typescript // Problem scenario -const sandbox = getSandbox(env.Sandbox, 'MyProject-123'); +const sandbox = getSandbox(env.Sandbox, "MyProject-123"); // Durable Object ID: "MyProject-123" await sandbox.exposePort(8080, { hostname }); // Preview URL: 8080-myproject-123-token123.yourdomain.com @@ -90,8 +122,8 @@ await sandbox.exposePort(8080, { hostname }); **The solution**: Use `normalizeId: true` to lowercase IDs when creating sandboxes: ```typescript -const sandbox = getSandbox(env.Sandbox, 'MyProject-123', { - normalizeId: true +const sandbox = getSandbox(env.Sandbox, "MyProject-123", { + normalizeId: true, }); // Durable Object ID: "myproject-123" (lowercased) // Preview URL: 8080-myproject-123-token123.yourdomain.com @@ -266,4 +298,5 @@ This is **only required for local development**. In production, all container po - [Production Deployment](/sandbox/guides/production-deployment/) - Set up custom domains for production - [Expose Services](/sandbox/guides/expose-services/) - Practical patterns for exposing ports - [Ports API](/sandbox/api/ports/) - Complete API reference +- [Tunnels API](/sandbox/api/tunnels/) - Zero-config `*.trycloudflare.com` URLs as an alternative for development - [Security Model](/sandbox/concepts/security/) - Security best practices From 4998863b38ecfc986a2ed22a687a2788d178ebc1 Mon Sep 17 00:00:00 2001 From: Aron <263346377+aron-cf@users.noreply.github.com> Date: Tue, 19 May 2026 16:54:45 +0100 Subject: [PATCH 4/6] Update src/content/docs/sandbox/api/tunnels.mdx Co-authored-by: Naresh --- src/content/docs/sandbox/api/tunnels.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/sandbox/api/tunnels.mdx b/src/content/docs/sandbox/api/tunnels.mdx index 7d45333180f49ed..2e675707f3fefba 100644 --- a/src/content/docs/sandbox/api/tunnels.mdx +++ b/src/content/docs/sandbox/api/tunnels.mdx @@ -18,7 +18,7 @@ Use `sandbox.tunnels` for **quick development** and **deployments on `.workers.d ## Requirements -- **RPC transport.** Calling `sandbox.tunnels` on a route-based transport throws `"RPC transport required"`. See [Transport configuration](/sandbox/configuration/transport/). +- **RPC transport.** Calling `sandbox.tunnels` on HTTP/Websocket transports will throw `"RPC transport required"`. See [Transport configuration](/sandbox/configuration/transport/). - **glibc image variant.** The default, `python`, `opencode`, and `desktop` images ship `cloudflared`. The `musl`/Alpine variant does not — there is no upstream `cloudflared` build for musl at this time. ## Methods From c15aecb9d3894b0aec29929cca431ecd9a680fb4 Mon Sep 17 00:00:00 2001 From: Aron <263346377+aron-cf@users.noreply.github.com> Date: Tue, 19 May 2026 16:54:58 +0100 Subject: [PATCH 5/6] Update src/content/docs/sandbox/api/tunnels.mdx Co-authored-by: Naresh --- src/content/docs/sandbox/api/tunnels.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/sandbox/api/tunnels.mdx b/src/content/docs/sandbox/api/tunnels.mdx index 2e675707f3fefba..382478fb83632cf 100644 --- a/src/content/docs/sandbox/api/tunnels.mdx +++ b/src/content/docs/sandbox/api/tunnels.mdx @@ -25,7 +25,7 @@ Use `sandbox.tunnels` for **quick development** and **deployments on `.workers.d ### `tunnels.get()` -Return a tunnel record for `port`. Idempotent: repeated calls for the same port return the same record. On a cache miss, the SDK spawns a fresh `cloudflared` process inside the container and persists the record in Durable Object storage. +Return a tunnel record for `port`. The SDK spawns a fresh `cloudflared` process inside the container if not already running. The method is idempotent so repeated calls for the same port return the same record. ```ts const tunnel = await sandbox.tunnels.get(port: number): Promise From adca048825d992c179424845f71d94c33a81a592 Mon Sep 17 00:00:00 2001 From: Aron <263346377+aron-cf@users.noreply.github.com> Date: Tue, 19 May 2026 16:55:07 +0100 Subject: [PATCH 6/6] Update src/content/docs/sandbox/api/tunnels.mdx Co-authored-by: Naresh --- src/content/docs/sandbox/api/tunnels.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/sandbox/api/tunnels.mdx b/src/content/docs/sandbox/api/tunnels.mdx index 382478fb83632cf..662add384f9a7ef 100644 --- a/src/content/docs/sandbox/api/tunnels.mdx +++ b/src/content/docs/sandbox/api/tunnels.mdx @@ -33,7 +33,7 @@ const tunnel = await sandbox.tunnels.get(port: number): Promise **Parameters**: -- `port` — Port number inside the sandbox to expose (1024-65535, excluding reserved ports). The service must already be listening on `0.0.0.0:` inside the container. +- `port` — Port number inside the sandbox to expose (1024-65535, excluding reserved ports). The service to tunnel to must already be listening on `0.0.0.0:` inside the container. **Returns**: `Promise` — the tunnel record. See [`TunnelInfo`](#tunnelinfo).