diff --git a/EXAMPLES.md b/EXAMPLES.md
index 112a03d5a..8fe584bce 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -8,6 +8,7 @@ Runnable examples live in [`examples/`](./examples).
## Table of Contents
- [Devbox From Blueprint (Run Command, Shutdown)](#devbox-from-blueprint-lifecycle)
+- [Devbox Tunnel (HTTP Server Access)](#devbox-tunnel)
- [MCP Hub + Claude Code + GitHub](#mcp-github-tools)
@@ -39,6 +40,36 @@ yarn test:examples
**Source:** [`examples/devbox-from-blueprint-lifecycle.ts`](./examples/devbox-from-blueprint-lifecycle.ts)
+
+## Devbox Tunnel (HTTP Server Access)
+
+**Use case:** Create a devbox, start an HTTP server, enable a tunnel, and access the server from the local machine through the tunnel.
+
+**Tags:** `devbox`, `tunnel`, `networking`, `http`
+
+### Workflow
+- Create a devbox
+- Start an HTTP server inside the devbox
+- Enable a tunnel for external access
+- Make an HTTP request from the local machine through the tunnel
+- Validate the response
+- Shutdown the devbox
+
+### Prerequisites
+- `RUNLOOP_API_KEY`
+
+### Run
+```sh
+yarn tsn -T examples/devbox-tunnel.ts
+```
+
+### Test
+```sh
+yarn test:examples
+```
+
+**Source:** [`examples/devbox-tunnel.ts`](./examples/devbox-tunnel.ts)
+
## MCP Hub + Claude Code + GitHub
diff --git a/examples/devbox-tunnel.ts b/examples/devbox-tunnel.ts
new file mode 100644
index 000000000..1ef1142f0
--- /dev/null
+++ b/examples/devbox-tunnel.ts
@@ -0,0 +1,105 @@
+#!/usr/bin/env -S npm run tsn -T
+
+/**
+---
+title: Devbox Tunnel (HTTP Server Access)
+slug: devbox-tunnel
+use_case: Create a devbox, start an HTTP server, enable a tunnel, and access the server from the local machine through the tunnel.
+workflow:
+ - Create a devbox
+ - Start an HTTP server inside the devbox
+ - Enable a tunnel for external access
+ - Make an HTTP request from the local machine through the tunnel
+ - Validate the response
+ - Shutdown the devbox
+tags:
+ - devbox
+ - tunnel
+ - networking
+ - http
+prerequisites:
+ - RUNLOOP_API_KEY
+run: yarn tsn -T examples/devbox-tunnel.ts
+test: yarn test:examples
+---
+*/
+
+import { RunloopSDK } from '@runloop/api-client';
+import { wrapRecipe, runAsCli } from './_harness';
+import type { RecipeContext, RecipeOutput } from './types';
+
+const HTTP_SERVER_PORT = 8080;
+const SERVER_STARTUP_DELAY_MS = 2000;
+
+export async function recipe(ctx: RecipeContext): Promise {
+ const { cleanup } = ctx;
+
+ const sdk = new RunloopSDK({
+ bearerToken: process.env['RUNLOOP_API_KEY'],
+ });
+
+ const devbox = await sdk.devbox.create({
+ name: 'devbox-tunnel-example',
+ launch_parameters: {
+ resource_size_request: 'X_SMALL',
+ },
+ });
+ cleanup.add(`devbox:${devbox.id}`, () => sdk.devbox.fromId(devbox.id).shutdown());
+
+ // Start a simple HTTP server inside the devbox using Python's built-in http.server
+ // We use execAsync because the server runs indefinitely until stopped
+ const serverExecution = await devbox.cmd.execAsync(
+ `python3 -m http.server ${HTTP_SERVER_PORT} --directory /tmp`,
+ );
+
+ // Give the server a moment to start
+ await new Promise((resolve) => setTimeout(resolve, SERVER_STARTUP_DELAY_MS));
+
+ // Enable a tunnel to expose the HTTP server
+ // For authenticated tunnels, use auth_mode: 'authenticated' and include the auth_token
+ // in your requests via the Authorization header: `Authorization: Bearer ${tunnel.auth_token}`
+ const tunnel = await devbox.net.enableTunnel({ auth_mode: 'open' });
+
+ // Get the tunnel URL for the server port
+ const tunnelUrl = await devbox.getTunnelUrl(HTTP_SERVER_PORT);
+
+ // Make an HTTP request from the LOCAL MACHINE through the tunnel to the devbox
+ // This demonstrates that the tunnel allows external access to the devbox service
+ const response = await fetch(tunnelUrl);
+ const responseText = await response.text();
+
+ // Stop the HTTP server
+ await serverExecution.kill();
+
+ return {
+ resourcesCreated: [`devbox:${devbox.id}`],
+ checks: [
+ {
+ name: 'tunnel was created successfully',
+ passed: !!tunnel.tunnel_key,
+ details: `tunnel_key=${tunnel.tunnel_key}`,
+ },
+ {
+ name: 'tunnel URL was constructed correctly',
+ passed: tunnelUrl.includes(tunnel.tunnel_key) && tunnelUrl.includes(`${HTTP_SERVER_PORT}`),
+ details: tunnelUrl,
+ },
+ {
+ name: 'HTTP request through tunnel succeeded',
+ passed: response.ok,
+ details: `status=${response.status}`,
+ },
+ {
+ name: 'response contains directory listing',
+ passed: responseText.includes('Directory listing'),
+ details: responseText.slice(0, 200),
+ },
+ ],
+ };
+}
+
+export const runDevboxTunnelExample = wrapRecipe({ recipe });
+
+if (require.main === module) {
+ void runAsCli(runDevboxTunnelExample);
+}
diff --git a/examples/registry.ts b/examples/registry.ts
index ab58c4638..5e5f9ac76 100644
--- a/examples/registry.ts
+++ b/examples/registry.ts
@@ -4,6 +4,7 @@
*/
import type { ExampleResult } from './types';
import { runDevboxFromBlueprintLifecycleExample } from './devbox-from-blueprint-lifecycle';
+import { runDevboxTunnelExample } from './devbox-tunnel';
import { runMcpGithubToolsExample } from './mcp-github-tools';
export interface ExampleRegistryEntry {
@@ -22,6 +23,13 @@ export const exampleRegistry: ExampleRegistryEntry[] = [
requiredEnv: ['RUNLOOP_API_KEY'],
run: runDevboxFromBlueprintLifecycleExample,
},
+ {
+ slug: 'devbox-tunnel',
+ title: 'Devbox Tunnel (HTTP Server Access)',
+ fileName: 'devbox-tunnel.ts',
+ requiredEnv: ['RUNLOOP_API_KEY'],
+ run: runDevboxTunnelExample,
+ },
{
slug: 'mcp-github-tools',
title: 'MCP Hub + Claude Code + GitHub',