Beginner-first production tutorial (Ubuntu + Docker Compose)

Self-host the LetsChat backend

LetsChat is not a single server. It is a small backend stack: chat state, auth, voice signaling, file storage, and startup discovery. This guide explains what each part does, why it matters, and then walks you through one of two deployment paths.

The same stack serves both ways to connect: the installable desktop app and a hosted browser client at app.<domain> — no install required, voice included.

SpacetimeDB Auth Service LiveKit MinIO Browser Client Discovery URL

What Runs In Production (And Why)

SpacetimeDB

Real-time chat/game state backend. Required for channels, messages, membership, and sync.

Auth Service

Login/session API and token minting. Required for user authentication and LiveKit token creation.

LiveKit

Voice/media signaling and transport. Required for voice channels and real-time audio.

MinIO

S3-compatible object storage. Required for uploads, attachments, and download URLs.

Module Init

One-shot container that publishes the SpacetimeDB WASM module on startup. Runs automatically and exits cleanly.

Web Client (app.<domain>)

Optional hosted browser SPA. Builds the React bundle pinned to this instance, so users can run LetsChat in a browser — voice included — without installing the desktop app.

Discovery Endpoint (/.well-known/letschat.json)

Served by core-api on auth.<domain>, so app startup auto-discovers your backend from one URL (for example https://auth.example.com). No separate connect subdomain. Without this, users must paste a full join link manually.

Before applying setup or upgrade steps, review Breaking Changes to avoid version mismatch and migration issues.

Choose Your Deployment Path

Both paths deploy the same backend stack. The difference is how HTTPS traffic reaches your server.

Common Mistakes

Problem Likely cause Fix
Voice joins but no audio LiveKit media ports not forwarded Forward 44382/udp (primary) and 44381/tcp (fallback) to host
Setup discovery fails auth.<domain> not serving /.well-known/letschat.json Tunnel: auth → core-api:8787; Caddy: set AUTH_DOMAIN + DNS
Browser client can't download files MINIO_CORS_ALLOW_ORIGIN doesn't match the web origin Set it to https://app.<domain> (or *)
Upload/download errors Wrong MINIO_PUBLIC_ENDPOINT Set to public https://files... endpoint clients can reach
Caddy cert issuance fails Cloudflare proxy enabled or closed 80/443 Use DNS-only records and open 80/tcp, 443/tcp
LiveKit setup fails immediately Wrong signalling scheme (ws:// vs wss://) Both tracks use wss://lk... — signalling is TLS-terminated (Cloudflare tunnel ingress or Caddy). Plain ws:// is dev-only
Spacetime connect timeout module-init failed or SpacetimeDB not yet healthy Check docker logs letschat-module-init; it retries on failure automatically