Skip to content

Commit cab71bc

Browse files
committed
feat: added tunnels example
1 parent d2b32e9 commit cab71bc

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

EXAMPLES.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Runnable examples live in [`examples/`](./examples).
99

1010
- [Blueprint with Build Context](#blueprint-with-build-context)
1111
- [Devbox From Blueprint (Run Command, Shutdown)](#devbox-from-blueprint-lifecycle)
12+
- [Devbox Tunnel (HTTP Server Access)](#devbox-tunnel)
1213
- [MCP Hub + Claude Code + GitHub](#mcp-github-tools)
1314

1415
<a id="blueprint-with-build-context"></a>
@@ -70,6 +71,36 @@ uv run pytest -m smoketest tests/smoketests/examples/
7071

7172
**Source:** [`examples/devbox_from_blueprint_lifecycle.py`](./examples/devbox_from_blueprint_lifecycle.py)
7273

74+
<a id="devbox-tunnel"></a>
75+
## Devbox Tunnel (HTTP Server Access)
76+
77+
**Use case:** Create a devbox, start an HTTP server, enable a tunnel, and access the server from the local machine through the tunnel. Uses the async SDK.
78+
79+
**Tags:** `devbox`, `tunnel`, `networking`, `http`, `async`
80+
81+
### Workflow
82+
- Create a devbox
83+
- Start an HTTP server inside the devbox
84+
- Enable a tunnel for external access
85+
- Make an HTTP request from the local machine through the tunnel
86+
- Validate the response
87+
- Shutdown the devbox
88+
89+
### Prerequisites
90+
- `RUNLOOP_API_KEY`
91+
92+
### Run
93+
```sh
94+
uv run python -m examples.devbox_tunnel
95+
```
96+
97+
### Test
98+
```sh
99+
uv run pytest -m smoketest tests/smoketests/examples/
100+
```
101+
102+
**Source:** [`examples/devbox_tunnel.py`](./examples/devbox_tunnel.py)
103+
73104
<a id="mcp-github-tools"></a>
74105
## MCP Hub + Claude Code + GitHub
75106

examples/devbox_tunnel.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env -S uv run python
2+
"""
3+
---
4+
title: Devbox Tunnel (HTTP Server Access)
5+
slug: devbox-tunnel
6+
use_case: Create a devbox, start an HTTP server, enable a tunnel, and access the server from the local machine through the tunnel. Uses the async SDK.
7+
workflow:
8+
- Create a devbox
9+
- Start an HTTP server inside the devbox
10+
- Enable a tunnel for external access
11+
- Make an HTTP request from the local machine through the tunnel
12+
- Validate the response
13+
- Shutdown the devbox
14+
tags:
15+
- devbox
16+
- tunnel
17+
- networking
18+
- http
19+
- async
20+
prerequisites:
21+
- RUNLOOP_API_KEY
22+
run: uv run python -m examples.devbox_tunnel
23+
test: uv run pytest -m smoketest tests/smoketests/examples/
24+
---
25+
"""
26+
27+
from __future__ import annotations
28+
29+
import asyncio
30+
31+
import httpx
32+
33+
from runloop_api_client import AsyncRunloopSDK
34+
35+
from ._harness import run_as_cli, wrap_recipe
36+
from .example_types import ExampleCheck, RecipeOutput, RecipeContext
37+
38+
HTTP_SERVER_PORT = 8080
39+
SERVER_STARTUP_DELAY_S = 2
40+
41+
42+
async def recipe(ctx: RecipeContext) -> RecipeOutput:
43+
"""Create a devbox, start an HTTP server, enable a tunnel, and access it from the local machine."""
44+
cleanup = ctx.cleanup
45+
46+
sdk = AsyncRunloopSDK()
47+
48+
devbox = await sdk.devbox.create(
49+
name="devbox-tunnel-example",
50+
launch_parameters={
51+
"resource_size_request": "X_SMALL",
52+
"keep_alive_time_seconds": 60 * 10,
53+
},
54+
)
55+
cleanup.add(f"devbox:{devbox.id}", devbox.shutdown)
56+
57+
# Start a simple HTTP server inside the devbox using Python's built-in http.server
58+
# We use exec_async because the server runs indefinitely until stopped
59+
server_execution = await devbox.cmd.exec_async(f"python3 -m http.server {HTTP_SERVER_PORT} --directory /tmp")
60+
61+
# Give the server a moment to start
62+
await asyncio.sleep(SERVER_STARTUP_DELAY_S)
63+
64+
# Enable a tunnel to expose the HTTP server
65+
# For authenticated tunnels, use auth_mode="authenticated" and include the auth_token
66+
# in your requests via the Authorization header: `Authorization: Bearer {tunnel.auth_token}`
67+
tunnel = await devbox.net.enable_tunnel(auth_mode="open")
68+
69+
# Get the tunnel URL for the server port
70+
tunnel_url = await devbox.get_tunnel_url(HTTP_SERVER_PORT)
71+
72+
# Make an HTTP request from the LOCAL MACHINE through the tunnel to the devbox
73+
# This demonstrates that the tunnel allows external access to the devbox service
74+
async with httpx.AsyncClient() as client:
75+
response = await client.get(tunnel_url)
76+
response_text = response.text
77+
78+
# Stop the HTTP server
79+
await server_execution.kill()
80+
81+
return RecipeOutput(
82+
resources_created=[f"devbox:{devbox.id}"],
83+
checks=[
84+
ExampleCheck(
85+
name="tunnel was created successfully",
86+
passed=bool(tunnel.tunnel_key),
87+
details=f"tunnel_key={tunnel.tunnel_key}",
88+
),
89+
ExampleCheck(
90+
name="tunnel URL was constructed correctly",
91+
passed=tunnel.tunnel_key in tunnel_url and str(HTTP_SERVER_PORT) in tunnel_url,
92+
details=tunnel_url,
93+
),
94+
ExampleCheck(
95+
name="HTTP request through tunnel succeeded",
96+
passed=response.is_success,
97+
details=f"status={response.status_code}",
98+
),
99+
ExampleCheck(
100+
name="response contains directory listing",
101+
passed="Directory listing" in response_text,
102+
details=response_text[:200],
103+
),
104+
],
105+
)
106+
107+
108+
run_devbox_tunnel_example = wrap_recipe(recipe)
109+
110+
111+
if __name__ == "__main__":
112+
run_as_cli(run_devbox_tunnel_example)

examples/registry.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Callable, cast
99

1010
from .example_types import ExampleResult
11+
from .devbox_tunnel import run_devbox_tunnel_example
1112
from .mcp_github_tools import run_mcp_github_tools_example
1213
from .blueprint_with_build_context import run_blueprint_with_build_context_example
1314
from .devbox_from_blueprint_lifecycle import run_devbox_from_blueprint_lifecycle_example
@@ -29,6 +30,13 @@
2930
"required_env": ["RUNLOOP_API_KEY"],
3031
"run": run_devbox_from_blueprint_lifecycle_example,
3132
},
33+
{
34+
"slug": "devbox-tunnel",
35+
"title": "Devbox Tunnel (HTTP Server Access)",
36+
"file_name": "devbox_tunnel.py",
37+
"required_env": ["RUNLOOP_API_KEY"],
38+
"run": run_devbox_tunnel_example,
39+
},
3240
{
3341
"slug": "mcp-github-tools",
3442
"title": "MCP Hub + Claude Code + GitHub",

0 commit comments

Comments
 (0)