Isorun Docs
Sandboxes

Port forwarding

Reach any port your in-sandbox code is listening on over HTTPS. Authenticated and WebSocket-aware.

sandbox.url(port) returns a URL that proxies to whatever your sandbox is listening on at that port. Start a vite dev server, a jupyter notebook, a gradio app, or any HTTP server inside the sandbox and reach it over HTTPS.

The URL is authenticated, not public. It lives under /v1, so every request needs your Authorization: Bearer <API key> header — a browser opening it directly gets 401. Proxy it from your own backend, or attach the header from your client, rather than handing the raw URL to an end user.

Preview a dev server

TypeScript
import { Isorun } from 'isorun'

const isorun = new Isorun()
const sandbox = await isorun.create({ image: 'node:22-slim' })

await sandbox.exec('git clone https://github.com/me/my-app && cd my-app && npm install')

// Start vite in the background. nohup + setsid + redirect so the exec
// call returns immediately and the server keeps running.
await sandbox.exec('cd my-app && nohup setsid npm run dev >/dev/null 2>&1 &')

// Reach the dev server through the runner.
const url = sandbox.url(5173)
console.log(url)
// → https://run-<region>.isorun.ai/v1/runs/<runID>/proxy/5173/

const res = await fetch(url, {
  headers: { Authorization: `Bearer ${process.env.ISORUN_API_KEY}` },
})

URL format

The URL is a path on the runner that already serves your API:

<apiUrl>/v1/runs/<runID>/proxy/<port>/<path>?<query>
ComponentMeaning
<apiUrl>Runner base URL, isorun.apiUrl — derived from your key's region
<runID>Sandbox ID returned by sandbox.id
<port>TCP port your in-sandbox server is listening on
<path>Forwarded verbatim to the in-sandbox server
<query>Forwarded verbatim

So .../v1/runs/run0123456789abcdef/proxy/8000/foo/bar?q=1 becomes a request to localhost:8000/foo/bar?q=1 inside the sandbox.

sandbox.url(port, path) takes an optional second argument for the path, so sandbox.url(8000, '/foo/bar') builds the same URL for you.

What passes through the proxy

  • All HTTP methods. GET, POST, PUT, DELETE, PATCH, anything.
  • Streaming responses. SSE, chunked encoding, etc. pass through without buffering.
  • WebSocket upgrades. Vite HMR, Jupyter kernel WS, hot-reload, notebook websockets, gradio's queue WS, langchain streaming, all work end-to-end.
  • Cookies. Forwarded both directions.
  • Large request and response bodies. No size cap from the proxy.

Authentication model

The URL is anonymous: anyone with the URL can hit it. The sandbox ID in the host is high-entropy, so it's effectively unguessable unless you share it.

The sandbox ID is the only credential on a public URL. Treat any URL you generate as a bearer secret: sharing it grants access to that port.

If you need stronger auth in front of your in-sandbox service, run it behind a reverse proxy with auth inside the sandbox itself, or ship a per-port allowlist via your own gateway.

What gets added to the forwarded request:

  • X-Forwarded-Host
  • X-Forwarded-Proto: https
  • X-Real-IP: <client IP>

Performance

Most of the round-trip is network distance between the client and the runner. For traffic that needs the lowest latency, hold a persistent connection to the URL: HTTP keep-alive, WebSocket, or streaming SSE.

Limits

ConstraintValue
Ports per sandboxany TCP port your code listens on
Method supportall HTTP methods including PATCH and CUSTOM
WebSocket supportyes, end-to-end
Authnone; the sandbox ID is the credential

Next steps

On this page