Fly.io
What you'll have at the end: a Vura app deployed to Fly.io with persistent machines — the recommended host for hot routes (WebSockets, in-memory state).
Supports all route kinds: serverless, hot (WebSocket), task.
This deploy flow was live-verified on real Fly infrastructure on 2026-06-11 (preflight for this guide).
Why Fly for hot routes
Fly's persistent machines never idle-stop, which means in-memory WebSocket state survives across requests. The emitted fly.toml sets auto_stop_machines = "off" and min_machines_running = 1 so Fly never tears down your process between connections.
Steps
1. Scaffold and build
npm create vura@latest my-app
cd my-app
npm install
npm run build
vura build emits dist/fly.toml and dist/Dockerfile when hot routes are present.
2. The fly.toml
dist/fly.toml is generated by vura build. Its contents:
# Generated by vura build — hand-edits will be overwritten on next build.
# Deploy with: fly deploy ./dist
# (flyctl picks up this fly.toml from ./dist and uses ./dist as build context)
app = "my-app"
kill_signal = "SIGTERM"
kill_timeout = "30s"
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 3000
force_https = true
# auto_stop_machines is off because hot routes hold in-memory websocket state
auto_stop_machines = "off"
min_machines_running = 1
Replace my-app with your Fly app name (or let fly apps create do it).
3. Create a Fly app (first deploy only)
fly apps create my-app
Skip fly launch — this guide uses fly deploy ./dist directly with the emitted fly.toml.
4. Deploy
fly deploy ./dist
flyctl reads dist/fly.toml, uses dist/ as the Docker build context (the same Dockerfile as the Docker guide), builds the image on Fly's builders, and rolls out the machine.
Expected output:
==> Building image
--> Building image done
==> Pushing image to the registry
==> Creating release
--> release v1 created
==> Deploying release v1
1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v1 deployed successfully
5. Verify
fly status
curl -fsS https://my-app.fly.dev/api/hello
Smoke test
# Static page
curl -fsS https://my-app.fly.dev/ | grep -q '<h1'
# API route
curl -fsS https://my-app.fly.dev/api/hello
# WebSocket
wscat -c wss://my-app.fly.dev/api/chat
Drain on deploy
kill_signal = "SIGTERM" and kill_timeout = "30s" are set in the emitted fly.toml. When Fly rolls a new machine, it sends SIGTERM to the old one. The Vura server handles SIGTERM by stopping new connections and waiting up to 30 seconds for in-flight requests and active WebSocket connections to close before exiting. Connections that remain open after 30 seconds are closed with code 1001 (Going Away).
There is no drainTimeoutMs config key — the 30-second value comes from kill_timeout in fly.toml. Adjust it there if your workload needs more time.
CI-tested: this guide is verified by the
flyjob in.github/workflows/selfhost.yml. The job extracts thefly.tomlfrom this guide and validates it withflyctl config validate. It then builds the same Docker image (from the docker guide's Dockerfile) and boots it locally to confirm/and/api/helloserve. It does not deploy to Fly — no cloud credentials are available in CI; Fly networking and anycast behavior are out of scope.
Route kind support
All kinds: serverless, hot (WebSocket), task / cron. Fly is the recommended host when your project has hot routes.