Reference

Self-hosting

The signaling server is a single Rust (Axum/Tokio) binary backed by Redis. Run it on a $5 VPS, point the SDK at it, and the entire realtime stack is yours: no usage caps, no vendor.

Requirements

Run with Podman / Docker

# Redis
podman run -d --name dendri-redis -p 6379:6379 docker.io/redis:7

# Signaling server
podman run -d --name dendri \
  -p 9876:9876 \
  ghcr.io/afterrealism/dendri-server:latest \
  --host 0.0.0.0 --port 9876 \
  --redis_url redis://host.containers.internal:6379 \
  --enable_relay

A production-grade Podman Quadlet stack (server + Redis + coturn + website, with systemd units) lives in dendri-infrastructure, including Terraform for VM provisioning.

Run from source

git clone https://github.com/afterrealism/dendri-server
cd dendri-server
cargo build --release

# Local development
cargo run -- --host 127.0.0.1 --port 9876 --enable_relay --allow_discovery

# With TLS terminated by the server itself
cargo run -- --host 0.0.0.0 --port 443 \
  --sslkey /path/to/key.pem --sslcert /path/to/cert.pem \
  --enable_relay

Key configuration flags

Every flag can also be set via environment variable; the full reference is in the server repo.

FlagEnvDefaultPurpose
--portPORT9000Listen port.
--host::Bind address.
--keyDENDRI_KEYdendriConnection key namespace (must match the client's key).
--pathPEERSERVER_PATH/URL prefix (must match the client's path).
--redis_urlredis://127.0.0.1:6379State backend.
--enable_relayDENDRI_ENABLE_RELAYfalseTier-4 encrypted WebSocket relay.
--allow_discoveryDENDRI_ALLOW_DISCOVERYfalseEnable the GET /:key/peers listing endpoint.
--turn_secretTURN_SECRETHMAC secret for minting ephemeral TURN credentials.
--turn_serversTURN server URLs handed to clients.
--jwt_secretDENDRI_JWT_SECRETRequire and validate JWTs from clients.
--sslkey / --sslcertTerminate TLS in the server itself.
--corsAllowed CORS origins (repeatable).
--concurrent_limit10000Max simultaneous clients.

HTTP endpoints

MethodPathDescription
GET/healthLiveness probe.
GET/metricsPrometheus metrics.
GET/{base}/:key/idGenerate a peer ID.
GET/{base}/:key/peersList peers (requires --allow_discovery).
GET/{base}/:key/turn-credentialsEphemeral TURN credentials (requires --turn_secret).
GET/{base}/dendriWebSocket upgrade; the primary signaling transport.
GET/{base}/http/sseServer-Sent Events fallback transport.
POST/{base}/http/sendHTTP send (pairs with SSE / long-poll).
GET/{base}/http/pollHTTP long-poll fallback transport.

TURN with coturn

Without TURN, peers behind symmetric NAT (many mobile carriers, some offices) can't establish direct connections. Run coturn next to the server with a shared HMAC secret:

listening-port=3478
fingerprint
use-auth-secret
static-auth-secret=YOUR_SHARED_SECRET
realm=turn.example.com
--turn_secret YOUR_SHARED_SECRET \
--turn_servers turn:turn.example.com:3478

Clients then opt in with fetchTurnCredentials: true: the SDK fetches short-lived credentials from /turn-credentials before connecting, so no static TURN passwords ever ship in your frontend bundle.

Pointing the SDK at your server

const store = createDendriStore({
	url: "wss://signal.example.com", // your domain
	fetchTurnCredentials: true,
	enableRelay: true,
	signalingTransport: "auto",
});
The client API is identical whether you self-host or use the hosted tier; only the URL (and apiKey) changes. No feature gates.

Scaling and operations

Licensing

ComponentLicenseWhat it means for you
@afterrealism/dendri-client, @afterrealism/dendri-y Apache-2.0 Use, modify, and embed in any app (commercial or closed-source) with attribution.
dendri-server, dendri-infrastructure AGPL-3.0 Run it freely. If you modify it and offer it as a network service, you must publish your modifications under AGPL.

Running the unmodified server for your own product is fine and requires nothing beyond keeping the license notice. If you need to modify the server and keep those changes private while offering it as a service, a commercial license is available from Afterrealism: get in touch.

Deployment questions? Open an issue on GitHub.