| name | testing-preview |
| description | Test the preview proxy feature end-to-end. Use when verifying preview URL changes, auth changes on the web proxy route, or networking-related fixes. |
Testing the Preview Feature
What It Does
The preview proxy exposes a port running inside a guest Firecracker VM at a public URL. Two implementations exist:
- Path-based (
/v1/machines/{id}/web/{port}/{path...}) — works over SSH tunnel and without wildcard DNS
- Subdomain-based (
<id>--<port>.<PreviewBase>) — requires Caddy on-demand TLS + wildcard DNS
Prerequisites
- boringd must be running with
BORING_NET=1 (enables guest networking via tap/bridge/DHCP)
- A test VM must be created with
net: true (ensures DHCP lease is assigned)
- An HTTP server must be running inside the guest on a known port
How to Set Up a Test VM
cd boringd && go build -o /tmp/boringd .
sudo BORING_NET=1 BORING_JAILER=0 BORING_TOKEN=test-token /tmp/boringd &
curl -s http://localhost:8080/v1/machines -X POST \
-H "Authorization: Bearer test-token" \
-d '{"template":"python","ttl_seconds":900,"net":true}'
python3 -c "
import websocket, time
ws = websocket.create_connection('ws://localhost:8080/v1/machines/MACHINE_ID/tty',
header=['Authorization: Bearer test-token'])
time.sleep(0.5)
ws.send(b'cd / && python3 -m http.server 8000 --bind 0.0.0.0 &\n')
time.sleep(2)
ws.close()
"
Key Test Cases
The server above is started from / (cd /), so http.server serves the guest's
root filesystem — that makes the sub-path test below resolve.
- Preview without auth:
curl http://localhost:8080/v1/machines/{id}/web/8000/ should return content (the / directory listing; no auth header needed)
- Other routes still require auth:
curl http://localhost:8080/v1/machines/{id} should return 401
- Sub-path routing:
curl http://localhost:8080/v1/machines/{id}/web/8000/etc/ should show the guest's /etc directory listing (proves sub-paths are proxied through)
- Via Vite proxy:
curl http://localhost:5173/boring/v1/machines/{id}/web/8000/ should work
Architecture Notes
- The web proxy route is intentionally unauthenticated — preview URLs are opened via
window.open in new browser tabs which can't add Authorization headers
- The machine ID acts as the access token (unguessable)
machineIP() resolves guest IP: first checks driver.ip (for forks), then falls back to DHCP lease file (/var/lib/misc/dnsmasq.leases)
- Guest MAC is derived from machine ID via SHA1:
guestMAC(id) → 06:00:XX:XX:XX:XX
Common Failure Modes
- "this computer isn't on the network": BORING_NET not set, or machine created without net=true (for snapshot-eligible templates)
- "nothing is listening on port X": Server not started in guest, or bound to 127.0.0.1 instead of 0.0.0.0
- 401 on preview URL: The route might have been accidentally wrapped in
s.auth() again
- Machine TTL expired: Default TTL is short; use 900s for testing
Devin Secrets Needed
- None required for local testing (BORING_TOKEN is set at runtime for test isolation)