LevelChatDocs
Docs
On-prem deployment

On-prem deployment

Self-host on Docker, k3s, or your own Kubernetes.

LevelChat self-host runs the same images as our managed cloud. The only difference is the license you point the platform at — your plan's entitlements gate per-room caps, recording, AI features, and other premium capabilities. There is no "Community Edition vs. Enterprise Edition" feature drift.

How self-host works

text
1. Buy license   →  levelchat.io/self-host  (Stripe checkout)
2. Receive email →  contains your license JWT + a download URL
3. Run installer →  one docker compose command
4. Done          →  your workspace at https://<your-domain>/console

Total time from purchase to a working room: ~5 minutes.

Prereqs

  • A Linux box with Docker Engine 24+ and docker compose v2 (Hetzner CPX21 / DigitalOcean Premium 4G / EC2 t3.large all work).
  • A public DNS A-record pointing at the box (e.g. app.acme.com → 167.235.x.x).
  • An SMTP relay for transactional emails — SendGrid, Mailgun, AWS SES, or any provider speaking STARTTLS + AUTH PLAIN on port 587. Required for email verification, member invitations, and password reset.
  • Outbound TCP 443 to license.levelchat.io for daily license heartbeats (~1 KB/day).

Install

~
# 1. Download the installer (link comes in your purchase email)
curl -fsSL "$LEVELCHAT_DOWNLOAD_URL" | tar -xz
cd levelchat-self-host

# 2. Configure
cp .env.example .env
$EDITOR .env
# Required values:
#   LC_PUBLIC_DOMAIN=app.acme.com
#   LC_LETSENCRYPT_EMAIL=ops@acme.com
#   LICENSE_JWT=<paste from your purchase email>
#   SMTP_HOST=smtp.mailgun.org
#   SMTP_USERNAME=postmaster@mg.acme.com
#   SMTP_PASSWORD=<your relay secret>
#   SMTP_FROM=noreply@acme.com
#   POSTGRES_PASSWORD=<generate a strong password>

# 3. Bring everything up
docker compose up -d

# 4. Verify
docker compose ps           # all containers should report 'Up (healthy)'
curl https://api.app.acme.com/healthz
curl https://app.acme.com/console/login

What just got deployed

ComponentContainerPurpose
Studio (admin frontend)studioCustomer-facing management console
LandinglandingYour app.acme.com marketing surface
DocsdocsDeveloper documentation site
AuthauthSign in, sign up, sessions, email verification
Admin APIadmin-apiStudio's read/write backend
SignalingsignalingWebSocket signaling for the SDK
Media SFUmedia-sfumediasoup SFU — bytes flow through this
RecordingrecordingComposes and stores recordings
LicenselicenseVerifies your license JWT, runs heartbeat
PostgrespostgresAll persistent state
RedisredisSessions, rate limits, ephemeral state
NATSnatsInternal pub/sub between services
MinIOminioS3-compatible recording storage
TraefiktraefikTLS termination via Let's Encrypt

Total: ~14 containers. RAM at idle: ~2.5 GB. CPU floor: ~5 % of one core.

First steps after install

  1. Open the Studio: https://app.<your-domain>/console/signup
  2. Sign up with your email — you'll receive a verification link from your SMTP relay.
  3. Verify → land on the dashboard → create your first project.
  4. Generate an API key under Settings → Developer and start integrating the SDK.

License heartbeat

license-svc phones home to https://license.levelchat.io/v1/heartbeat once a day with:

  • Your license JWT (so we can confirm it's still active)
  • An instance fingerprint (machine-id + hostname) — used to detect license sharing across unrelated clusters
  • Aggregate quota counters (rooms-created, participant-minutes, recording-GB) — used for billing on usage-priced plans

If the heartbeat is blocked for 7 consecutive days the instance enters a degraded mode (existing rooms keep working, new rooms refuse). 30 days → frozen (read-only). Heartbeats are small enough (~1 KB) that even a quarterly air-gapped export+import is feasible — see LICENSE_HEARTBEAT_OFFLINE=true in .env.example.

What stays in your perimeter

  • All recording bytes. Recordings live in your local MinIO (or your own S3 bucket if you set S3_ENDPOINT=... in .env). LevelChat never sees them.
  • All chat messages. Persisted in your Postgres.
  • All identities + rooms + members metadata. Entirely on your DB.
  • All viewer fan-out for ≤2k-viewer broadcasts. The cascade SFU edge for >10k viewers is the only path that calls back to our cloud — and it's opt-in.

The only data that leaves your perimeter is the daily license heartbeat (license JWT + fingerprint + counters). No PII, no media bytes.

Cloud-augmented features (optional)

A handful of premium features call back to our cloud — they're opt-in and clearly marked in Studio → Settings:

  • AI transcription (captions.levelchat.io — audio streamed for transcription)
  • AI summary / agent (agents.levelchat.io)
  • Cascade SFU for ≥10k-viewer broadcasts (your origin SFU pushes to our edge)
  • AV1 transcode-on-egress (raw recording uploaded to our transcoder, returned compressed)

Self-host installs come with all four off by default. Enabling them counts against your plan's per-meter quotas.

Operating the install

Backup

~
docker exec lc-postgres pg_dump -U levelchat levelchat | gzip > "/backups/pg-$(date +%F).sql.gz"
docker run --rm --network lc-net minio/mc \
  alias set lc http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
docker run --rm --network lc-net -v /backups:/out minio/mc \
  mirror lc/lc-recordings /out/recordings/

restic against /var/lib/docker/volumes/ is the no-thinking equivalent.

Upgrades

~
docker compose pull       # pulls newest tagged versions for the same major
docker compose up -d
# DB migrations run automatically on auth + admin-api boot.

Pin a version for change-control:

~
LC_VERSION=1.4.2 docker compose up -d

Rollback

~
LC_VERSION=1.4.1 docker compose up -d --force-recreate

DB migrations are intentionally additive — rolling back the binary against a forward-migrated DB works for the previous minor version. For multi-version rollbacks, restore Postgres from a pg_dump first.

Logs

~
docker compose logs -f auth admin-api signaling     # filter by service
docker compose logs --since 1h | grep ERROR         # quick triage

For production observability, the docker-compose.self-host-ha.yml variant ships a Grafana + Loki bundle pre-wired. The small mode skips it to keep RAM low.

Three deployment sizes

ModeBest forCost ballparkTLSHA
docker-compose.self-host-small.ymlDemos, internal tools, ≤200 concurrent$20–40/moTraefik + Let's Encryptno
docker-compose.self-host-ha.ymlProduction self-host, ≤2k concurrent$90–200/moTraefik + Let's Encryptpostgres replica + redis sentinel
infra/kubernetes/helm/levelchat≥2k concurrent or multi-regionvariescert-managerbring your own

Pick self-host-small.yml for the first install — you can migrate to the HA compose or Helm chart later without changing the application configuration.

Need help?

  • Email: self-host@levelchat.io
  • Status page: status.levelchat.io — incidents on the license + downloads endpoints (the only services that affect your install)
  • Runbook: the installer ships a RUNBOOK.md covering the 12 most common operational scenarios (full disk, missed heartbeat, DB migration stuck, …)