When developing software on your devbox, you will often want to expose local services running on your devbox to the outside world.
For example, you may want to have your agent start a local web server to serve a frontend application and then expose the live frontend to your users.
Other examples include:
- remotely collaborating on a frontend project
- testing a web service
- accessing a Jupyter notebook running on your devbox
- accessing a local database running on your devbox
Let’s use devbox tunnels to securely access ports on your devbox over a simple url.
Supported Protocols
devbox tunnels support multiple protocols, making them suitable for a wide variety of applications:
- HTTP/HTTPS: Standard web traffic for REST APIs, web applications, and static content
- WebSockets: Real-time bidirectional communication for chat applications, live updates, and interactive features
- Server-Sent Events (SSE): One-way real-time communication from server to client for live data streams and notifications
You will need to explicitly specify the hostname 0.0.0.0 within your service
to expose ports to the outside world. Using other IP addresses or localhost is
incompatible with tunnels.
Setting up a tunnel
There are two ways to set up a lunnel: at devbox creation time, or after the devbox is running.
Option 1: Enable tunnel at devbox creation
The simplest approach is to enable the tunnel when creating the devbox. The tunnel will be automatically provisioned and available when the devbox is ready.
devbox = await runloop.devbox.create(
tunnel={"auth_mode": "open"}, # or "authenticated"
entrypoint="python3 -m http.server 8080 --bind 0.0.0.0"
)
# Access tunnel information from the devbox
tunnel = devbox.tunnel
print(f"Tunnel URL for port 8080: https://8080-{tunnel.tunnel_key}.tunnel.runloop.ai")
Option 2: Enable tunnel on a running devbox
You can also enable a tunnel on an existing running devbox using the enable_tunnel method.
Create a devbox
Create a devbox and start a service on it.devbox = await runloop.devbox.create(
entrypoint="python3 -m http.server 8080 --bind 0.0.0.0"
)
Enable the tunnel
Enable a tunnel on the running devbox.tunnel = await runloop.api.devboxes.enable_tunnel(
devbox.id,
auth_mode="open" # or "authenticated"
)
# Construct the URL for your service
url = f"https://8080-{tunnel.tunnel_key}.tunnel.runloop.ai"
print(f"Access your service at: {url}")
Tunnel URLs follow this format:
https://{port}-{tunnel_key}.tunnel.runloop.ai
Where:
{port} is the port number your service is running on (e.g., 8080, 3000)
{tunnel_key} is the encrypted key returned when you enable the tunnel
For example, if your tunnel key is abc123xyz and your service runs on port 3000:
https://3000-abc123xyz.tunnel.runloop.ai
You can access any port on your devbox by changing the port number in the URL, as long as your service is bound to 0.0.0.0.
Authentication Modes
Tunnels support two authentication modes:
Open Mode (Public Access)
With auth_mode: "open", anyone with the URL can access your tunnel. This is useful for:
- Sharing live previews with collaborators
- Testing webhooks from external services
- Public demos
tunnel = await runloop.api.devboxes.enable_tunnel(
devbox.id,
auth_mode="open"
)
Authenticated Mode (Restricted Access)
With auth_mode: "authenticated", requests must include a bearer token. This is useful for:
- Sensitive development environments
- APIs that should not be publicly accessible
- Secure internal tools
tunnel = await runloop.api.devboxes.enable_tunnel(
devbox.id,
auth_mode="authenticated"
)
# The auth_token is only returned for authenticated tunnels
print(f"Auth token: {tunnel.auth_token}")
print(f"URL: https://8080-{tunnel.tunnel_key}.tunnel.runloop.ai")
To access an authenticated tunnel, include the token in your requests:
curl -H "Authorization: Bearer <auth_token>" \
https://8080-<tunnel_key>.tunnel.runloop.ai
While the devbox is active and the tunnel is enabled, the URL has remote
access to all of your devbox ports. Treat tunnel URLs with the same care you
would treat any exposed endpoint.
Tunnel Lifecycle
- One tunnel per devbox: Each devbox can have one tunnel enabled at a time.
- Persistent until shutdown: Once enabled, tunnels remain active until the devbox is shut down.
- Survives suspend/resume: If you suspend and resume a devbox, the tunnel information is preserved (you’ll need to re-enable the tunnel after resume if needed).
- Multiple ports: A single tunnel allows access to any port on your devbox - just change the port number in the URL.
Example: Complete Tunnel Workflow
Here’s a complete example showing how to create a devbox, start a web server, and access it via a tunnel:
import asyncio
from runloop_api_client import AsyncRunloopSDK
runloop = AsyncRunloopSDK()
async def main():
# Create devbox with tunnel enabled at launch
devbox = await runloop.devbox.create(
tunnel={"auth_mode": "open"},
entrypoint="python3 -m http.server 8080 --bind 0.0.0.0"
)
# Get the tunnel URL
tunnel_url = f"https://8080-{devbox.tunnel.tunnel_key}.tunnel.runloop.ai"
print(f"Your web server is accessible at: {tunnel_url}")
# Keep running until user interrupts
try:
print("Press Ctrl+C to shutdown...")
await asyncio.sleep(float('inf'))
except KeyboardInterrupt:
pass
finally:
await devbox.shutdown()
print("devbox shut down")
asyncio.run(main())