VIEWPORT
Self-host

Deploy the relay

One Docker container, plus DNS and TLS. Stateless, JWT-validated, restartable any time.

This page is the single-container deployment quickstart. For the broader story of what self-hosting the relay gets you, see Self-host overview.

What the relay actually is

viewport/services/relay/, node 22 multi-stage Dockerfile.

Properties:

  • Operationally stateless. Per-process state (in-memory connection registry, IP rate-limit counters, recent log buffer, metrics counters) is lost on restart. No payloads persisted.
  • WebSocket upgrade only at GET /ws. Validated with a Zod schema for query params (role, workspaceId, runtimeTargetId?).
  • Frames are E2EE. Validated by relay-frame-validation.ts; the relay can identify envelopes (workspace, runtime target) but cannot decrypt.
  • Auth is JWT-based. The relay calls the platform's /api/runtime/internal/relay/validate to admit each upgrade.
  • Cross-relay backplane for multi-instance deploys: RELAY_BACKPLANE_MODE=server|redis.

Minimum viable deployment

docker run -d \
  --name viewport-relay \
  --restart unless-stopped \
  -p 7781:7781 \
  -e RELAY_LISTEN_PORT=7781 \
  -e RELAY_INTERNAL_KEY="<shared secret with platform>" \
  -e RELAY_VALIDATE_URL="https://api.getviewport.com/api/runtime/internal/relay/validate" \
  -e RELAY_JWKS_URL="https://api.getviewport.com/api/.well-known/jwks.json" \
  ghcr.io/viewportai/relay:latest

Point your DNS / load balancer at the container and terminate TLS in front (the relay itself can run plain WS behind your TLS terminator).

Configuration

The full env-var set is documented in services/relay/README.md in the OSS repo. The most important ones:

EnvPurpose
RELAY_LISTEN_PORTPort to bind
RELAY_INTERNAL_KEYShared secret used to authenticate calls to the platform's relay-internal endpoints
RELAY_VALIDATE_URLWhere to validate JWTs on each upgrade
RELAY_JWKS_URLWhere to fetch signing keys
RELAY_ADMIN_TOKENBearer token to access /state, /logs, /metrics
RELAY_ENABLE_ADMIN_HTTP1 to expose admin endpoints; default off
RELAY_BACKPLANE_MODEsingle | server | redis
RELAY_BACKPLANE_URLBackplane endpoint (when server or redis)
RELAY_CLIENT_REDIRECT_ENABLED1 to redirect clients to the relay holding their daemon (multi-instance)

Admin endpoints

When RELAY_ENABLE_ADMIN_HTTP=1:

MethodPathAuthReturns
GET/healthnone{ok, service, uptimeMs, tlsEnabled?, relayMode?, relayId?}
GET/statebearerSnapshot of registered workspaces, daemon connection state, client counts
GET/logsbearerRecent log buffer (?summary=1 for short form)
GET/metricsbearerPrometheus exposition

If admin is disabled the routes return 404, not 401. Informationally indistinguishable from a non-existent server.

Pointing the daemon at your relay

vpd remote login --relay-url wss://relay.your-co.com/ws

Or per-workspace by overriding ~/.viewport/config.json daemon.relay.endpoint. See CLI · remote.

Multi-relay topologies

Two backplane modes for running more than one relay:

  • server: persistence-backed bus through the platform API. relay_bus_frames and workspace_relay_presences tables on the platform side fan frames between relays. Use when relays are in different regions and Redis is too much to operate.
  • redis: pub/sub bus on a shared Redis. Lower latency, requires Redis ops.

When the same workspace has a daemon on relay-A and a browser on relay-B, the backplane carries the frames. With RELAY_CLIENT_REDIRECT_ENABLED=1, the relay tells the browser to reconnect to the relay that holds its daemon. Keeps the hot path single-hop.

What's still hosted

These calls still go to api.getviewport.com even when running your own relay:

  • All user-facing REST routes (/api/workspaces, /api/plans, /api/inbox-items, etc.)
  • Daemon → platform sync (/api/runtime/*)
  • Relay → platform JWT validation (/api/runtime/internal/relay/validate)
  • Audit log writes

If you need any of those on-prem, the relay-self-host path is not enough. Get in touch.

Crypto for the security-curious

The wire envelope between daemon and relay can be either:

  • noise-ik: Noise IK pattern
  • noise-ikpsk2: Noise IKpsk2 (selected when workspace policy_mode = 'crypto_pairing')

Conformance vectors: packages/daemon/docs/relay-noise-v3-conformance-vectors.json. Run npm run test:conformance to replay them against the daemon's compiled handshake code.

On this page