Your chat. Your server. Your rules.
Chatalot is a self-hosted chat platform for friends, teams, and communities who refuse to hand their conversations to corporations. Real-time messaging, voice and video calls, end-to-end encryption, and a desktop app — all running on hardware you control.
No data harvesting. No algorithmic feeds. No subscription tiers to unlock basic features. Just a fast, modern chat experience that belongs entirely to you.
Our mission: Chatalot was born from a simple belief — your conversations are yours. Not a corporation's asset, not a data point, not a product. This is an independent, passion-driven project built for the long haul, with no corporate ties, no investors, and no exit strategy. Just software that respects its users. Read the full mission statement →
Most chat platforms make you choose: convenience or privacy. Centralized services own your data and can change the rules whenever they want. Federated alternatives promise freedom but deliver complexity — running a node shouldn't require a systems engineering degree.
Chatalot takes a different approach: one Docker command, and you're live. A single binary serves the API, WebSocket connections, and the web UI. Add the desktop app for native performance. Invite your people with access codes. Done.
:shortcode: syntax, autocomplete in composerFor a complete feature list, see Feature Status. For full documentation, see the Chatalot Documentation.
All messages are end-to-end encrypted. DMs use the Signal protocol (X3DH + Double Ratchet), group channels use Sender Keys — both compiled to WASM and running in the browser. Keys are generated at registration, sessions are persisted in IndexedDB, and the server acts as an untrusted relay.
Chatalot is built for people who take privacy seriously. Here's what that means in practice:
All messages are end-to-end encrypted before leaving your device. The server stores only encrypted ciphertext — even someone with full database access sees nothing but random bytes.
Passwords are hashed with Argon2id (64 MiB memory cost, 3 iterations, 4 parallel lanes). The original password cannot be recovered from the hash — not by admins, not by attackers, not by anyone.
Chatalot collects zero telemetry. There are no analytics scripts, no usage tracking, no crash reporting, and no phone-home behavior. The software communicates only with its own database.
Forgot your password? Use your recovery code — generated at registration, no email infrastructure needed. No admin intervention required. Your recovery code resets your password and generates a fresh code in one step.
| The server can see | The server cannot see |
|---|---|
| Who sent a message and when | Message content (encrypted) |
| File metadata (size, type) | File contents (encrypted) |
| Your username and email | Your password (hashed) |
| When you're online | What you're typing |
| Channel membership | Private DM conversations |
| Login timestamps and IPs | 2FA secrets (encrypted at rest) |
git clone https://github.com/purpleneutral/chatalot.git
cd chatalot
./scripts/generate-secrets.sh
This creates JWT signing keys (secrets/) and a .env file with a random database password and encryption key.
docker compose up -d
Two containers come up:
Navigate to http://localhost:8080. Since registration defaults to invite-only, the first user must set REGISTRATION_MODE=open in .env (or use the deploy script which handles this). After creating the admin account, switch back to invite_only and generate invite codes from the admin panel.
To allow open registration:
# In .env, set:
REGISTRATION_MODE=open
# Then restart:
docker compose up -d
| Mode | Behavior |
|---|---|
invite_only (default) |
Users need a valid invite code to register |
open |
Anyone can register |
closed |
Registration is completely disabled |
Admins generate invite codes from the admin panel. Codes can have usage limits and expiration dates.
Building from source on ARM devices takes 20+ minutes (Rust compilation). Pre-built multi-arch images are available on GHCR:
# Use the pre-built image instead of building locally
# In docker-compose.override.yml:
services:
chatalot:
image: ghcr.io/purpleneutral/chatalot:latest
build: !reset null
Then run docker compose up -d as normal. The correct architecture (amd64 or arm64) is selected automatically.
docker compose --profile quick-tunnel up -d
This spins up a cloudflared container that creates a temporary public URL. Check the logs for your URL:
docker compose logs cloudflared-quick | grep trycloudflare
Share that URL with your friends — it works immediately, no DNS or certificates to configure.
# Add your tunnel token to .env
echo "CLOUDFLARE_TUNNEL_TOKEN=your_token" >> .env
# Start with the production profile
docker compose --profile production up -d
The server listens on port 8080. Create a docker-compose.override.yml (gitignored):
services:
chatalot:
labels:
- "traefik.enable=true"
- "traefik.http.routers.chatalot.rule=Host(`chat.example.com`)"
- "traefik.http.routers.chatalot.entrypoints=websecure"
- "traefik.http.routers.chatalot.tls=true"
- "traefik.http.services.chatalot.loadbalancer.server.port=8080"
networks:
- proxy-network
networks:
proxy-network:
external: true
WebSocket connections at /ws are proxied automatically.
Native desktop clients are built with Tauri 2.0. They connect to any Chatalot server — just enter the URL on first launch.
Check the Releases page for:
.deb, .rpm.exe)# Install Tauri CLI
cargo install tauri-cli
# Install web dependencies
cd clients/web && npm install && cd ../..
# Build (this also builds the web frontend automatically)
cd clients/desktop/src-tauri && cargo tauri build
Requires Rust 1.84+, Node.js 22+, and platform-specific dependencies (WebKitGTK on Linux, WebView2 on Windows).
| Layer | Technology |
|---|---|
| Server | Rust (axum + tokio) |
| Database | PostgreSQL 17 |
| Web Client | Svelte 5 + Tailwind CSS |
| Desktop Client | Tauri 2.0 |
| E2E Encryption | X3DH + Double Ratchet, ChaCha20-Poly1305 (Rust → WASM) |
| Auth | Argon2id passwords, Ed25519-signed JWTs, refresh token rotation |
| Voice/Video | WebRTC mesh (up to 25 participants) |
| Deployment | Docker Compose, Cloudflare Tunnel |
The server is designed as an untrusted relay — it stores and routes messages but is architecturally separated from plaintext content.
chatalot/
├── crates/
│ ├── chatalot-server/ # axum HTTP/WS server
│ │ └── src/
│ │ ├── routes/ # REST API endpoints
│ │ ├── ws/ # WebSocket handler + connection manager
│ │ ├── middleware/ # JWT auth, rate limiting, security headers
│ │ └── services/ # Auth service, business logic
│ ├── chatalot-db/ # Database layer (repository pattern)
│ │ └── src/
│ │ ├── models/ # Rust structs matching DB tables
│ │ └── repos/ # Query functions per entity
│ ├── chatalot-crypto/ # E2E encryption library
│ │ └── src/
│ │ ├── x3dh.rs # X3DH key agreement
│ │ ├── double_ratchet.rs
│ │ ├── sender_keys.rs
│ │ └── aead.rs # ChaCha20-Poly1305
│ ├── chatalot-crypto-wasm/ # WASM bindings for browser crypto
│ └── chatalot-common/ # Shared types (API DTOs, WS messages)
├── clients/
│ ├── web/ # Svelte 5 SPA
│ │ └── src/
│ │ ├── lib/api/ # REST client
│ │ ├── lib/crypto/ # E2E crypto (WASM loader, IndexedDB, session manager)
│ │ ├── lib/ws/ # WebSocket client
│ │ ├── lib/stores/ # Svelte 5 rune-based state (17 stores)
│ │ ├── lib/components/# Reusable UI components
│ │ ├── lib/utils/ # Emoji data, helpers
│ │ ├── lib/webrtc/ # WebRTC call manager
│ │ └── routes/ # Pages
│ └── desktop/ # Tauri 2.0 wrapper
├── docs/ # Detailed documentation
├── migrations/ # PostgreSQL migrations (42 files)
├── scripts/
│ ├── install.sh # Interactive setup wizard
│ ├── deploy.sh # Automated deploy (commit, push, pull, rebuild)
│ ├── generate-secrets.sh # Generate JWT keys + .env
│ ├── generate-keys.sh # Generate JWT keys only
│ └── build-wasm.sh # Build WASM crypto module for web client
├── .github/workflows/ # CI: multi-arch Docker image builds (amd64 + arm64)
├── Dockerfile # Multi-stage build
└── docker-compose.yml
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
required | PostgreSQL connection string |
JWT_PRIVATE_KEY_PATH |
./secrets/jwt_private.pem |
Ed25519 private key |
JWT_PUBLIC_KEY_PATH |
./secrets/jwt_public.pem |
Ed25519 public key |
TOTP_ENCRYPTION_KEY |
optional | Hex key for encrypting TOTP secrets at rest |
REGISTRATION_MODE |
invite_only |
open, invite_only, or closed |
ADMIN_USERNAME |
optional | Username that gets admin privileges |
LISTEN_ADDR |
0.0.0.0:8080 |
Server bind address |
FILE_STORAGE_PATH |
./data/files |
Encrypted file storage directory |
MAX_FILE_SIZE_MB |
100 |
Max upload size in MB |
RUST_LOG |
info |
Log level |
VAPID_PRIVATE_KEY |
optional | Base64-encoded ECDSA P-256 private key for web push notifications |
VAPID_PUBLIC_KEY |
optional | Base64-encoded ECDSA P-256 public key for web push notifications |
CLOUDFLARE_TUNNEL_TOKEN |
optional | For production Cloudflare Tunnel profile |
TENOR_API_KEY |
optional | Google API key for GIF search (get one free) |
# Database
docker compose up postgres -d
# Server
cp .env.example .env
./scripts/generate-secrets.sh
cargo run
# Build WASM crypto module (required before web client)
./scripts/build-wasm.sh
# Web client (separate terminal)
cd clients/web
npm install
npm run dev
cargo test # 59 unit tests (crypto, auth, security, CSS sanitizer)
cargo clippy # Lint checks
cd clients/web && npm run check # Svelte type checking
Note: The Docker build handles the WASM compilation automatically —
build-wasm.shis only needed for local development.
# Full deploy: commit, push, pull on server, rebuild containers
./scripts/deploy.sh "your commit message"
# Just pull and restart on server
./scripts/deploy.sh --pull-only
Configure with environment variables: DEPLOY_HOST, DEPLOY_DIR, DEPLOY_GIT_URL. See scripts/deploy.sh for all options.
git clone <repo-url> chatalot && cd chatalot
./scripts/generate-secrets.sh
docker compose up -d --build
Running a Chatalot instance means you're responsible for the people who use it. Here's what that looks like:
data/ directory regularly. Messages are encrypted, but metadata (users, channels, memberships) is not.data/terms-of-service.md and data/privacy-policy.md to override them.Joining a Chatalot instance means trusting the person who runs it. Here's what you should know:
If Chatalot is useful to you, consider buying me a coffee:
GPL-3.0 — see LICENSE for details.