Ports & previews
Expose a port inside a sandbox at a public preview URL. Three visibility tiers — public, private (qbox auth), and time-limited signed links.
Run a web app, API, or dev server inside a sandbox and reach it from the outside at a predictable URL:
http://sb_<id>-<port>.preview.<your-domain>
Traffic flows straight from the edge proxy to the app in the guest — the control plane only handles routing and access, never the bytes. HTTP and WebSockets are supported. Previews are served over HTTP for now (HTTPS previews are deferred).
Expose a port at spawn
import qbox
sb = qbox.Sandbox.create(
"python-3.12",
ports=[qbox.PortSpec(8080), qbox.PortSpec(3000, visibility="private")],
)
# the forwarding starts routing once the sandbox boots; read the URLs back:
for p in sb.ports.list():
print(p.port, p.url)
Expose on a running sandbox
url = sb.ports.expose(8080) # public by default
sb.ports.expose(5432, visibility="private") # requires qbox auth
link = sb.ports.expose(9000, visibility="signed_url", ttl=3600) # shareable, expires
sb.ports.list()
sb.ports.unexpose(8080)
From the CLI:
# at create time — repeatable, PORT[:visibility]
qbox sandboxes create python-3.12 --port 8080 --port 3000:private
# on a running sandbox
qbox sandboxes ports expose sb_abc123 8080 --visibility public
qbox sandboxes ports list sb_abc123
From the dashboard: pick ports in the spawn dialog, or use the Ports tab on a running sandbox.
Visibility tiers
| Tier | Who can reach it |
|---|---|
public | anyone with the URL |
private | signed-in qbox users (optionally restricted to specific users) |
signed_url | anyone holding a time-limited signed link |
A signed link is minted when you expose with visibility="signed_url"; rotate it
any time:
fresh = sb.ports.mint_signed_url(9000, ttl=600)
Private previews need a shared cookie
private previews are gated by your dashboard login: the proxy validates the
qbox_session cookie against the control plane and, for a port with an
allowed_users list, checks you’re on it. For that to work the cookie has to
reach the preview subdomain, which means two settings:
QBOX_COOKIE_DOMAIN=<your-domain>— scopes the session cookie to your registrable domain so it’s shared with*.preview.<domain>(the installer sets this). Empty = host-only cookie, and a private preview just bounces you to the dashboard.QBOX_COOKIE_SECURE=false— because previews are served over HTTP for now, aSecurecookie wouldn’t be sent to them. This sends the session cookie over HTTP too, so only enable private previews on a trusted network. For sharing over an untrusted network, prefersigned_url, which needs no cookie.
Reserved ports
Ports a sandbox service already owns are reserved and rejected: 22 when SSH is
enabled, 8888 when the code interpreter is enabled. Privileged ports (<1024)
require force=True (the dashboard and CLI set it for you when you ask for one).
Self-hosting setup
Previews need a real domain with wildcard DNS, because each preview lives on its own subdomain. On a self-hosted install:
- Point
*.preview.<your-domain>(and<your-domain>) at your host’s public IP. - Set
QBOX_PROXY_DOMAIN=<your-domain>. The installer defaults this to your site domain and generates the signing + internal secrets automatically.
Caddy routes *.preview.<domain> to the proxy over plain HTTP — no certificates
or DNS-provider tokens to set up. An IP-only / bare-port deployment can’t serve
previews (there’s no domain to put them under). HTTPS previews are deferred; if
you front the proxy with TLS yourself, set QBOX_PROXY_SCHEME=https. See
Configuration for the full list.