Self-hosting the rendezvous server
dvai-bridge's distributed-inference feature has two ways to find peer devices to offload to:
- LAN — zero setup. Devices on the same Wi-Fi find each other via mDNS / Bonjour. No server needed.
- Internet — needs a small WebSocket relay. The rendezvous server. You self-host it. Two devices on different networks pair via QR scan, then exchange encrypted inference traffic through the relay.
The rendezvous server is optional. Don't deploy one, don't set rendezvousUrl, and the internet path is off. Your app falls back to LAN-only. That's the right choice for many apps.
This page covers when to deploy a rendezvous server, the one-click deploy flow, and what you're signing up for operationally.
Should you deploy one?
Yes, if your users will routinely:
- Have a phone on cellular and a laptop at home, and want the laptop to do the heavy lifting for the phone's inference.
- Use multiple devices on different networks belonging to the same user — BYOD enterprise scenarios, family-account apps.
- Demo your app at a conference where the venue's Wi-Fi blocks mDNS and the user's devices need to pair via QR plus cellular.
No, if:
- Your users only ever run inference on one device at a time.
- Your users are always on the same network as their other devices. LAN mDNS already covers this.
- You don't want to operate a small (cheap, but real) piece of infrastructure.
Architecture summary
The server is a thin WebSocket relay:
- Two devices connect. One displays a QR, the other scans it.
- Server mediates X25519 key exchange — just relays public keys.
- Devices derive a shared secret independently. They AEAD-encrypt all subsequent traffic.
- Server forwards opaque encrypted payloads back and forth.
- Server never sees plaintext inference data. It can't, even if compromised.
- Stateless beyond per-session memory. Restarts wipe sessions.
Resource footprint is tiny. A $5/mo box handles thousands of concurrent pairings.
One-click deploy
Pick whichever platform you prefer. Both have referral programs that help fund dvai-bridge maintenance:
| Platform | Click to deploy | Cost (typical) |
|---|---|---|
| Railway | $5/mo Hobby | |
| DigitalOcean | $5/mo basic-xxs |
After deploy:
Note the URL the platform assigns —
*.up.railway.appor*.ondigitalocean.app.Set
RENDEZVOUS_URL=wss://<your-url>as an env var in the platform dashboard. Redeploy.Configure your dvai-bridge app to point at it:
tsconst dvai = new DVAI({ backend: "auto", offload: { enabled: true, rendezvousUrl: "wss://your-rendezvous.up.railway.app", // ... }, });
Done. Internet pairing works.
Other platforms
Railway and DigitalOcean are headlined because they pay us a referral commission. The server runs equally well on Fly.io, Render, Cloudflare Workers (with WebSocket support), Google Cloud Run, AWS App Runner, or any Linux VM with Docker. The platform-by-platform walkthrough below covers every supported host. The same content ships in the repo at rendezvous/DEPLOYMENT.md for people who land on the source first.
Fly.io
cd rendezvous
fly launch --copy-config --name dvai-bridge-rendezvous
# Edit fly.toml: set internal_port = 8080
fly deploy
fly status
# → URL is https://dvai-bridge-rendezvous.fly.devSet RENDEZVOUS_URL=wss://dvai-bridge-rendezvous.fly.dev via fly secrets set.
Render.com
- Connect your GitHub repo at https://render.com/.
- Create a new Web Service.
- Root directory:
rendezvous. - Build:
npm install && npm run build. - Start:
npm start. - Add env vars per
.env.example. - Render assigns a
*.onrender.comURL.
Vercel / Netlify
These platforms are optimised for static plus serverless functions. WebSockets aren't a great fit for their request-per-invocation model. Possible via their long-running compute tiers — not recommended. The Railway / DO / Fly / Render path is simpler.
If you must:
- Vercel — deploy as a Node serverless function with
export const maxDuration = 300and use the platform's WebSocket support (currently in preview). - Netlify — similar caveats. Use Netlify Functions with the
serverless-wsadapter.
For most teams, the higher-quality WebSocket support on Railway / DO / Fly / Render is worth the small monthly cost.
AWS
App Runner
# Create an ECR repo, push the Docker image, then:
aws apprunner create-service \
--service-name dvai-bridge-rendezvous \
--source-configuration ImageRepository={...}App Runner does WebSockets. It's pricier than Railway / DO at this workload size — typically $25-50/mo vs $5/mo.
ECS Fargate
For shops already on AWS infra. Build the Docker image, push to ECR, launch a Fargate service behind an ALB. WebSocket-aware target group. Same code, same env vars. Just more YAML.
Google Cloud Run
gcloud run deploy dvai-bridge-rendezvous \
--source . \
--port 8080 \
--allow-unauthenticated \
--region us-central1Cloud Run supports WebSockets up to 60-minute connections. Plenty for a 60-second pairing TTL.
Bare VM with Docker
# On any Linux box with Docker installed:
docker build -t dvai-bridge-rendezvous .
docker run -d \
--name rendezvous \
--restart unless-stopped \
-p 8080:8080 \
-e RENDEZVOUS_URL=wss://yourdomain.com \
-e ALLOWED_ORIGINS=https://yourapp.com \
dvai-bridge-rendezvousFront it with Caddy / Nginx / Traefik for TLS termination. Sample Caddyfile:
yourdomain.com {
reverse_proxy localhost:8080
}Caddy auto-provisions a Let's Encrypt cert. Done.
Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: dvai-bridge-rendezvous
spec:
replicas: 1 # see "scaling beyond one instance" below
selector:
matchLabels: { app: rendezvous }
template:
metadata:
labels: { app: rendezvous }
spec:
containers:
- name: rendezvous
image: ghcr.io/yourname/dvai-bridge-rendezvous:0.1.0
ports: [{containerPort: 8080}]
envFrom: [{configMapRef: {name: rendezvous-env}}]
livenessProbe:
httpGet: {path: /health, port: 8080}
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet: {path: /health, port: 8080}
periodSeconds: 10Custom domain
After your platform assigns a default URL, you'll probably want your own:
- In the platform dashboard — add the custom domain. The platform shows you a CNAME target.
- At your DNS registrar — create a CNAME record pointing
rendezvous.yourapp.comat the platform's CNAME target. - Wait ~5-15 min for the platform to provision a TLS cert. Let's Encrypt, auto on every major platform.
- Update
RENDEZVOUS_URLin your env vars towss://rendezvous.yourapp.com. Redeploy. - Update the dvai-bridge config in your app(s) to use the new URL.
Scaling beyond one instance
The current server stores sessions in a per-process Map. A second instance won't see the first's sessions. Pairing will fail ~50% of the time as source and target land on different replicas.
Workarounds:
- Sticky sessions — configure the platform's load balancer to pin a client to the same instance for the lifetime of the WS connection. Every major LB (HAProxy, ALB, Nginx, Cloudflare) supports this. Works fine until ~10k concurrent sessions per instance.
- Vertical scaling — bump the single instance's RAM / CPU. The server is small enough that one $20/mo instance comfortably handles ~50k concurrent sessions.
- Redis-backed session store — on the v3.2+ roadmap. Replace the in-memory
SessionStorewith one that reads/writes to Redis. Run as many instances as you want behind a non-sticky LB.
For most apps in v3.0's lifetime, vertical scaling plus sticky LB is plenty.
Operational checklist
- [ ] TLS is on —
wss://, notws://. Every managed platform handles this automatically. - [ ] Custom domain with auto-renewing cert.
- [ ]
ALLOWED_ORIGINSenv var locks down to your app's origin(s). Default*is fine for early testing; not OK in production. - [ ]
MAX_SESSIONSsized for your real load. Default 10000; 256 MB RAM handles this. - [ ] Monitoring — at minimum, alert on the
/healthendpoint returning non-200. Most platforms do this for you. - [ ] Restart-on-crash —
Dockerfile,railway.json, andapp.yamlall set this. Verify in your platform's settings. - [ ] Decide on log retention — server logs are minimal but include device IDs. Pick a retention policy that matches your privacy promises to users.
What about hosting a rendezvous server we run for you?
We don't. dvai-bridge is a library, not a service. Running a rendezvous server for the world would (a) make us a man-in-the-middle nobody asked for, (b) create an abuse vector we'd have to police, and (c) make our infrastructure part of every consumer's uptime story forever. The whole point of the library is that you own the inference stack. That includes the optional helper server.
The server code is small — a few hundred lines. Well-tested. Deploys in under 5 minutes. Do it once and forget it.
Troubleshooting
- Pairing connects then immediately drops. The platform's idle-WebSocket timeout is shorter than
SESSION_TTL_SECONDS. Tune the platform side or shortenSESSION_TTL_SECONDS. The client library sends periodic pings automatically once a session is active. Most platforms accept that as activity. /healthworks butwss://connection fails. Check that your platform routes WebSocket upgrades correctly. Most do; some require an explicit "WebSockets enabled" toggle.- CORS error in browser apps. Set
ALLOWED_ORIGINSto include the origin your browser app loads from. - Server occasionally crashes under load. Check memory.
MAX_SESSIONSdefaults to 10k assuming 256 MB. Sized smaller? Lower the cap. - WebSocket close 1006 at the client. The platform's idle-connection timeout is shorter than your
SESSION_TTL_SECONDS. Tune the platform side, or rely on the library's automatic ping frames (sent every 25 seconds once a session is active). - Pairing fails consistently across networks. Confirm
RENDEZVOUS_URLenv var matches the public URL the server is reachable at. If it's wrong, the QR payload encodes a URL the target can't connect to. /pairreturns 404. The@fastify/websocketplugin didn't load. Check thatnpm installcompleted cleanly — the plugin is a hard dep.
Updating the server
Rendezvous server versions follow the parent dvai-bridge repo's tags (v3.0.0, v3.0.1, etc.). Updates are backwards-compatible within a major version. To update on a managed platform: redeploy from main (or the latest tag). To update from Docker: docker pull the new image tag and docker restart.
The wire protocol (rendezvous/src/messages.ts) is versioned via the v: 1 field in QR payloads. Future protocol changes will bump the version and accept both old and new for a deprecation window.
Contributing changes to the server
The server lives at rendezvous/ in the dvai-bridge monorepo. PRs welcome — see CONTRIBUTING.md. The server has its own test suite (npm test from rendezvous/) and is deliberately small enough to read end-to-end in a sitting.
