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.

SpacetimeDB Auth Service LiveKit MinIO 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.

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

Lets app startup auto-discover your backend from one URL (for example https://connect.example.com). 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 44381/tcp+udp and 44382/udp to host
Setup discovery fails connect.<domain> not routed to discovery container Tunnel: connect → discovery:80; Caddy: set CONNECT_DOMAIN correctly
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 signaling scheme (ws:// vs wss://) Tunnel often uses ws://lk...:44380; Caddy usually uses wss://lk...
Spacetime connect timeout module-init failed or SpacetimeDB not yet healthy Check docker logs letschat-module-init; it retries on failure automatically