- TypeScript 45.7%
- Svelte 45.3%
- JavaScript 4.2%
- HTML 2.2%
- CSS 1.4%
- Other 1.2%
| .forgejo | ||
| .vscode | ||
| infra | ||
| landing | ||
| src | ||
| src-tauri | ||
| static | ||
| .gitignore | ||
| .npmrc | ||
| .prettierignore | ||
| .prettierrc | ||
| bun.lock | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| svelte.config.js | ||
| tsconfig.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
RyftBorn
A self-hosted, open-source Discord alternative. Own your community.
- Voids — Text channels
- Drifts — Voice/video channels with screen sharing
- Tethers — Direct messages
- Self-hosted — Your data, your server, your rules
Built with SvelteKit, PocketBase, LiveKit, and Tauri.
| RyftBorn | Discord Equivalent |
|---|---|
| Ryft | Server |
| Void | Text Channel |
| Drift | Voice Channel |
| Tether | DM |
Server Installation
Prerequisites
- A Linux server (VPS, dedicated, or local machine)
- Docker or Podman with Compose
- A domain name pointed at your server (for production with TLS)
- Ports 80, 443, 7881, 3478/udp, 50000-50200/udp open in your firewall
The examples below use
podman. Replace withdockerif that's what you have.
Podman setup (Linux only)
Podman needs a compose provider and Docker Hub registry configured:
pip3 install podman-compose
echo 'unqualified-search-registries = ["docker.io"]' >> /etc/containers/registries.conf
Quick Start (Local / Development)
This starts the full stack — PocketBase, the WebSocket server, LiveKit, and Caddy (which also builds the frontend image, so the first start takes a few minutes).
git clone https://git.askdavis.com/AskDavis/RyftBorn.git
cd RyftBorn/infra
# Generate secrets and start containers
./setup.sh
setup.sh will:
- Prompt for a PocketBase admin email and password
- Generate a secret for LiveKit
- Write the
.envfile - Start the containers
- Create the PocketBase superuser account
- Enable username login
Once complete, the services are available at:
| Service | URL |
|---|---|
| PocketBase | http://localhost:8090 |
| PB Admin UI | http://localhost:8090/_/ |
| WebSocket | ws://localhost:7891 |
| LiveKit | ws://localhost:7880 |
| Caddy | https://localhost (landing) · https://app.localhost (app) |
Run the frontend for development:
cd ..
bun install
bun run dev
Open http://localhost:5173 and register your first account. The first user automatically becomes the server owner.
Production Deployment
Production is the same stack pointed at a real domain: Caddy handles reverse proxying with automatic TLS, and LiveKit's embedded TURN server handles NAT traversal for voice/video. The frontend is built inside Docker — no Bun or Node needed on the server.
1. Clone and configure
git clone https://git.askdavis.com/AskDavis/RyftBorn.git
cd RyftBorn/infra
2. Run setup
./setup.sh
3. Set your domain
Edit infra/.env and set your domain (both variables — DOMAIN is used by LiveKit's TURN server, DOMAIN_FRONTEND is what Caddy serves):
DOMAIN=ryftborn.example.com
DOMAIN_FRONTEND=ryftborn.example.com
Optionally add a Klipy API key for GIF support:
VITE_KLIPY_API_KEY=your_key_here
4. Enable TURN and external IP for LiveKit
Still in infra/.env, enable NAT traversal for production:
USE_EXTERNAL_IP=true
TURN_ENABLED=true
LIVEKIT_NODE_IP=
LiveKit's config is generated inside
docker-compose.ymlfrom these env vars — you don't need to edit any LiveKit config file. LeaveLIVEKIT_NODE_IPempty in production;use_external_iphandles IP discovery.
5. DNS setup
Point two DNS records at your server:
| Type | Name | Value |
|---|---|---|
| A | ryftborn.example.com |
Your server IP |
| A | app.ryftborn.example.com |
Your server IP |
The Caddyfile also contains a
mail.{$DOMAIN}block that proxies to a Stalwart mail server container. If you don't run one, remove that block frominfra/Caddyfile.
6. Build and start everything
podman compose build
podman compose up -d
This builds the SvelteKit frontend inside Docker and starts all services:
| Service | What it does |
|---|---|
| PocketBase | Auth, database, file storage |
| WS Server | Real-time chat, typing indicators, presence |
| LiveKit | Voice/video channels, screen sharing, embedded TURN for NAT |
| Caddy | Reverse proxy, automatic HTTPS certificates |
Caddy automatically provisions TLS certificates once DNS resolves.
7. First-time app setup
- Open
https://app.ryftborn.example.com - Register the first account — you become the server owner
- The Setup Wizard walks you through naming your server, setting visibility, and creating your first channel
- Share the link with others so they can register and join
Architecture
Browser / Tauri App
│
├── HTTPS ──► Caddy (reverse proxy)
│ ├── /api/*, /_/* ──► PocketBase (auth, data, files)
│ ├── /ws ──► WS Server (real-time chat)
│ ├── /token ──► WS Server (LiveKit tokens)
│ ├── /preview ──► WS Server (link previews)
│ ├── /password-reset/* ──► WS Server (password reset)
│ ├── /rtc* ──► LiveKit (voice/video signaling)
│ └── /* ──► SvelteKit (frontend SPA)
│
└── UDP/TCP ──► LiveKit (WebRTC media on 50000-50200/udp + 7881/tcp,
embedded TURN on 3478/udp for NAT traversal)
In development (without Caddy), the app connects to each service directly via port:
- PocketBase:
http://localhost:8090 - WebSocket:
ws://localhost:7891 - LiveKit:
ws://localhost:7880
In production, everything goes through Caddy on a single domain with automatic TLS.
Firewall Ports
| Port | Protocol | Service | When needed |
|---|---|---|---|
| 80 | TCP | Caddy (HTTP redirect) | Production |
| 443 | TCP | Caddy (HTTPS) | Production |
| 3478 | UDP | LiveKit embedded TURN | Production |
| 7881 | TCP | LiveKit RTC (TCP fallback) | Production |
| 50000-50200 | UDP | LiveKit RTC media | Production |
| 8090 | TCP | PocketBase | Dev only |
| 7891 | TCP | WebSocket server | Dev only |
| 7880 | TCP | LiveKit signaling | Dev only |
In production, only ports 80, 443, 7881, 3478/udp, and 50000-50200/udp need to be open. Caddy handles all HTTP(S) routing; WebRTC media flows directly to LiveKit.
Common Operations
# View logs
podman compose logs -f # all services
podman compose logs -f pocketbase # single service
# Restart
podman compose down && podman compose up -d
# Rebuild frontend after code changes
podman compose build caddy
podman compose up -d caddy
# PocketBase admin UI
# Dev: http://localhost:8090/_/
# Prod: https://app.ryftborn.example.com/_/
# Back up data
cp -r infra/pocketbase/pb_data infra/pocketbase/pb_data.backup
# Reset everything (destroys all data)
podman compose down
rm -rf infra/pocketbase/pb_data
./setup.sh
Desktop App (Tauri)
The desktop app wraps the same SvelteKit frontend in a native window.
bun install
bun run tauri dev
When connecting from the desktop app, enter your server address (e.g. https://app.ryftborn.example.com) in the connection dialog. The app automatically derives WebSocket and LiveKit URLs from it.
Tech Stack
- Frontend: SvelteKit + Svelte 5 + TypeScript + Tailwind v4
- Desktop/Mobile: Tauri v2
- Backend: PocketBase (auth, CRUD, files, embedded SQLite)
- Real-time: Bun WebSocket server
- Voice/Video: LiveKit Server
- Reverse Proxy: Caddy (automatic TLS)
- NAT Traversal: LiveKit embedded TURN
- Icons: Lucide Svelte
- Package Manager: Bun
Project Structure
src/ # SvelteKit frontend (Svelte 5 + TypeScript + Tailwind v4)
src-tauri/ # Tauri v2 native shell
infra/ # Server infrastructure
docker-compose.yml # All services (LiveKit config generated inline from env vars)
Dockerfile.caddy # Multi-stage build (frontend + Caddy)
Caddyfile # Reverse proxy config
ws-server/ # Bun WebSocket server
pocketbase/
pb_migrations/ # Database schema
pb_data/ # Database + file storage (created at runtime)
setup.sh # First-time setup script
.env # Secrets (generated by setup.sh, not committed)
landing/ # Static landing page served at root domain
License
MIT — see LICENSE.