Configuration
Status: Complete
All environment variables and settings for the Chatalot server.
Environment Variables
Chatalot is configured entirely through environment variables. When using Docker, these are set in the .env file in the project root. The docker-compose.yml passes them to the container.
Required Variables
These must be set for the server to start.
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgres://chatalot:password@postgres:5432/chatalot |
Server
| Variable | Description | Default |
|---|---|---|
LISTEN_ADDR |
Address and port the server binds to | 0.0.0.0:8080 |
STATIC_FILES_PATH |
Path to the built Svelte SPA files | ./static (Docker: /app/static) |
PUBLIC_URL |
Public-facing URL of your instance (used in links, invites) | (none) |
RUST_LOG |
Log level filter (tracing-subscriber syntax) | chatalot_server=info,tower_http=info |
Authentication
| Variable | Description | Default |
|---|---|---|
JWT_PRIVATE_KEY_PATH |
Path to Ed25519 private key PEM file | ./secrets/jwt_private.pem (Docker: /run/secrets/jwt_private_key) |
JWT_PUBLIC_KEY_PATH |
Path to Ed25519 public key PEM file | ./secrets/jwt_public.pem (Docker: /run/secrets/jwt_public_key) |
TOTP_ENCRYPTION_KEY |
32-byte hex key for encrypting TOTP secrets at rest | Required -- server refuses to start without it. Generate with openssl rand -hex 32. |
Access tokens are valid for 15 minutes. Refresh tokens are valid for 30 days. These values are compiled into the binary and are not configurable at runtime.
Registration
| Variable | Description | Default |
|---|---|---|
REGISTRATION_MODE |
Controls who can register: open, invite_only, or closed |
invite_only |
ADMIN_USERNAME |
Username to automatically grant admin privileges on startup | (none) |
COMMUNITY_CREATION_MODE |
Who can create communities: anyone or admin_only |
admin_only |
File Uploads
| Variable | Description | Default |
|---|---|---|
FILE_STORAGE_PATH |
Directory where uploaded files are stored | ./data/files (Docker: /app/data/files) |
MAX_FILE_SIZE_MB |
Maximum file upload size in megabytes | 100 |
UPLOAD_QUOTA_MB |
Per-user upload quota in megabytes (0 = unlimited) | 500 |
OIDC / SSO
Chatalot supports single sign-on via any OpenID Connect provider (Authentik, Keycloak, Authelia, Zitadel, etc.). When configured, a Log in with SSO button appears on the login page alongside the standard email/password form.
| Variable | Description | Default |
|---|---|---|
OIDC_ISSUER_URL |
OIDC provider discovery URL. Must expose a /.well-known/openid-configuration endpoint. |
(none -- SSO disabled) |
OIDC_CLIENT_ID |
Client ID registered with your identity provider | (none) |
OIDC_CLIENT_SECRET |
Client secret from your identity provider | (none) |
OIDC_REDIRECT_URI |
The callback URL the provider redirects to after authentication. Must match the redirect URI configured in your provider. | (none) |
OIDC_DISABLE_PASSWORD_LOGIN |
Set to true to hide the email/password form and require all users to log in via SSO |
false |
SSO is enabled when OIDC_ISSUER_URL, OIDC_CLIENT_ID, and OIDC_CLIENT_SECRET are all set. If any of the three is missing, OIDC is silently disabled.
Quick Example: Authentik
- In Authentik, create a new OAuth2/OpenID Provider:
- Name:
chatalot - Authorization flow: implicit consent (or explicit, depending on your preference)
- Redirect URI:
https://chat.example.com/auth/oidc/callback -
Scopes:
openid,email,profile -
Create an Application linked to the provider, with the slug
chatalot. -
Set the environment variables:
-
Restart Chatalot:
User Provisioning
When a user logs in via OIDC for the first time, Chatalot automatically creates a local account using the email and preferred_username claims from the identity provider. If the username is already taken, a numeric suffix is appended.
OIDC users can still set a local password later from Settings > Security if you want to allow fallback authentication. If OIDC_DISABLE_PASSWORD_LOGIN=true, users who were provisioned via SSO will only be able to log in through the identity provider.
Notes
- The provider must support the Authorization Code flow.
- The
openid,email, andprofilescopes are required. - OIDC login respects the same rate limiting and account lockout rules as password login.
- Invite-only mode still applies: if
REGISTRATION_MODE=invite_only, first-time SSO users will need a valid invite code unless an admin has pre-created their account.
Integrations (Optional)
| Variable | Description | Default |
|---|---|---|
GIPHY_API_KEY |
API key for GIF search via Giphy | (none -- GIF search disabled) |
GITHUB_API_TOKEN |
GitHub personal access token for feedback issue creation | (none) |
GITHUB_REPO_OWNER |
GitHub repository owner for feedback issues | (none) |
GITHUB_REPO_NAME |
GitHub repository name for feedback issues | (none) |
Web Push Notifications (Optional)
| Variable | Description | Default |
|---|---|---|
VAPID_PRIVATE_KEY |
Base64-encoded ECDSA P-256 private key for web push | (none -- push disabled) |
VAPID_PUBLIC_KEY |
Base64-encoded ECDSA P-256 public key for web push | (none -- push disabled) |
If both keys are set, users can enable push notifications in Settings to receive DM alerts when the tab is closed. If omitted, the push feature is silently hidden.
Generate VAPID keys with:
Email / SMTP (Meeting Invites, Optional)
Chatalot sends .ics calendar invites when a meeting is scheduled, updated, or cancelled. Email is opt-in: if SMTP_HOST or EMAIL_FROM is not set, no emails are sent and all other features work normally.
| Variable | Description | Default |
|---|---|---|
SMTP_HOST |
Hostname of your SMTP relay | (none -- email disabled) |
SMTP_PORT |
SMTP port | 587 |
SMTP_USERNAME |
SMTP authentication username | (none) |
SMTP_PASSWORD |
SMTP authentication password. Also resolved from /run/secrets/smtp_password (Docker secret). |
(none) |
SMTP_TLS |
true = STARTTLS + auth (required for real relays); false = plaintext only (local catchers such as Mailpit -- never use against a live relay) |
true |
EMAIL_FROM |
Envelope and From: address |
(none -- email disabled) |
EMAIL_FROM_NAME |
Display name in the From: header |
Chatalot |
Email is enabled only when both SMTP_HOST and EMAIL_FROM are set. For relay setup, deliverability configuration (SPF/DKIM/DMARC), the container-to-host gotcha, and worked examples for SES, SendGrid, Mailgun, Postfix, and Proton Bridge, see Email / SMTP.
Voice & Video (TURN/STUN)
Chatalot uses WebRTC for voice and video calls. By default, it uses Google's public STUN servers to help peers discover each other and establish direct connections. This works for most home networks.
However, users behind restrictive corporate firewalls or symmetric NATs may not be able to connect directly. In these cases, a TURN server acts as a relay -- routing media traffic through a server that both peers can reach. Chatalot ships with an optional coturn TURN server in docker-compose.yml that you can enable when needed.
| Variable | Description | Default |
|---|---|---|
TURN_AUTH_SECRET |
Shared HMAC secret between chatalot-server and coturn. Required when running the turn profile. Generate with openssl rand -hex 32. |
(none) |
TURN_URLS |
Comma-separated TURN/TURNS URLs returned to clients (e.g. turns:turn.example.com:5349,turn:turn.example.com:3478). |
(none) |
TURN_CREDENTIAL_TTL_SECS |
How long generated TURN credentials remain valid, in seconds. Clamped to 60-86400. | 3600 |
TURN_EXTERNAL_IP |
Your server's public IP address, used by coturn for relay advertisement. | (none) |
TURN_LISTENING_PORT |
Plaintext STUN/TURN listener port. Override only if another host service owns the default. Must match the port in TURN_URLS and your firewall. |
3478 |
TURN_TLS_PORT |
TLS/DTLS STUN/TURN listener port. Override only if another host service owns the default. Must match the port in TURN_URLS and your firewall. |
5349 |
ICE_SERVERS |
Deprecated. JSON array of STUN/TURN servers. Kept for back-compat; prefer TURN_AUTH_SECRET+TURN_URLS. |
(none) |
Enabling the Built-in TURN Server
The coturn service uses a Docker Compose profile, so it does not start by default. Since v0.24.0, coturn uses ephemeral HMAC-signed credentials; there are no long-lived TURN passwords.
-
Set the required environment variables in
.env: -
Start Chatalot with the
turnprofile: -
Ensure the following ports are open in your firewall (swap in your
TURN_LISTENING_PORT/TURN_TLS_PORTvalues if you overrode the defaults): - 3478/tcp and 3478/udp -- plaintext STUN/TURN
- 5349/tcp -- TLS-over-TURN
- 5349/udp -- DTLS-over-TURN
-
49152-49200/udp -- Media relay range
-
First boot generates a self-signed cert in the
turn_certsnamed volume. To rotate,docker volume rm chatalot_turn_certsand restart the coturn container.
Clients fetch short-lived credentials at the start of each call via GET /api/account/turn-credentials. If the endpoint is unavailable or the deployment doesn't set TURN_AUTH_SECRET, clients fall back to STUN-only (public STUN is sufficient for most home networks).
See the coturn service definition in docker-compose.yml for the full configuration, including security hardening options like denied peer IP ranges and bandwidth limits.
Port Conflicts on the Host
The defaults 3478 (plaintext) and 5349 (TLS/DTLS) are the IANA STUN/TURN assignments and what most WebRTC stacks try first. Only override them when another process on the host already owns the port. The most common case is a Tailscale/Headscale DERP relay, which listens on UDP/3478 for its embedded STUN service — start coturn on that host and it will loop on Cannot bind ... Address already in use for the UDP side.
To work around a conflict, set alternate ports in .env:
Then update three things to match:
TURN_URLS— the ports your clients connect to:- Firewall rules — open the new ports, close the old ones if they're no longer needed.
- Reverse proxy / NAT mapping — if coturn is behind one, forward the overridden ports rather than
3478/5349.
Non-standard ports work, but corporate firewalls sometimes allowlist only the standard set. If you expect users on restrictive networks and can't keep 3478/5349 free, consider moving the conflicting service (headscale's STUN, for instance) instead of moving coturn.
Using a Custom ICE Configuration
If you already run your own TURN server or want to use a third-party service, set ICE_SERVERS without enabling the built-in coturn:
ICE_SERVERS='[{"urls":"stun:stun.l.google.com:19302"},{"urls":"turn:turn.example.com:3478","username":"myuser","credential":"mypassword"}]'
The value must be a valid JSON array. Each entry can include urls, username, and credential fields matching the RTCIceServer specification.
Tip: Most small deployments (friends, family, small teams) work fine with just STUN. Only enable TURN if users report connection failures from restrictive networks.
Cloudflare Tunnel (Optional)
| Variable | Description | Default |
|---|---|---|
CLOUDFLARE_TUNNEL_TOKEN |
Token for a named Cloudflare Tunnel | (none) |
Updater Sidecar (Managed Updates)
The chatalot-updater sidecar (run under the updater compose profile) has its own config surface. Most operators only need to know about the registry credentials secret.
| Env / secret path | Description |
|---|---|
secrets/registry_creds (mounted at /run/secrets/registry_creds) |
JSON {"username":"…","password":"…","serveraddress":"…"}. Forwarded on every docker pull via X-Registry-Auth so the sidecar can authenticate against a private image registry. If the file is absent or whitespace-only, pulls are anonymous — suitable for public registries and for deployments that don't use managed updates. Present-but-malformed JSON is a fatal startup error (better than silently falling back to anon and then 401'ing at apply time). See secrets/registry_creds.example in the repo. |
For the canonical Seglamater-hosted channel, clients receive a read-only per-instance token during onboarding (write scopes are vendor-only). The username is the client account, the password is that token.
Docker Compose Only
These variables are used by docker-compose.yml but not by the server binary directly:
| Variable | Description | Default |
|---|---|---|
DB_PASSWORD |
PostgreSQL password (used to construct DATABASE_URL) |
(auto-generated by setup scripts) |
Example .env File
# Database
DATABASE_URL=postgres://chatalot:your_secure_password@postgres:5432/chatalot
DB_PASSWORD=your_secure_password
# JWT signing keys (Docker paths)
JWT_PRIVATE_KEY_PATH=/run/secrets/jwt_private_key
JWT_PUBLIC_KEY_PATH=/run/secrets/jwt_public_key
# 2FA encryption key (generate with: openssl rand -hex 32)
TOTP_ENCRYPTION_KEY=a1b2c3d4e5f6...
# Server
LISTEN_ADDR=0.0.0.0:8080
RUST_LOG=chatalot_server=info,tower_http=info
# Files
FILE_STORAGE_PATH=/app/data/files
MAX_FILE_SIZE_MB=100
UPLOAD_QUOTA_MB=500
# Registration
REGISTRATION_MODE=invite_only
ADMIN_USERNAME=alice
COMMUNITY_CREATION_MODE=admin_only
# Public URL (if behind a reverse proxy)
PUBLIC_URL=https://chat.example.com
# OIDC / SSO (optional)
OIDC_ISSUER_URL=https://auth.example.com/application/o/chatalot/
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
OIDC_REDIRECT_URI=https://chat.example.com/auth/oidc/callback
OIDC_DISABLE_PASSWORD_LOGIN=false
# Web push notifications (optional -- generate with: npx web-push generate-vapid-keys)
VAPID_PRIVATE_KEY=
VAPID_PUBLIC_KEY=
# Outbound email / SMTP for meeting invites (optional -- see email-smtp.md).
# Email is disabled unless BOTH SMTP_HOST and EMAIL_FROM are set.
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_USERNAME=noreply@example.com
# SMTP_PASSWORD=your-smtp-password # or mount /run/secrets/smtp_password
# SMTP_TLS=true # false only for local catchers (Mailpit)
# EMAIL_FROM=noreply@example.com
# EMAIL_FROM_NAME=Chatalot
# Voice/video TURN relay (optional -- see "Voice & Video (TURN/STUN)" section above).
# Ephemeral HMAC scheme (since v0.24.0); also enable the `turn` compose profile.
# TURN_AUTH_SECRET= # generate with: openssl rand -hex 32 (shared with coturn)
# TURN_URLS=turns:turn.example.com:5349,turn:turn.example.com:3478
# TURN_EXTERNAL_IP= # your server's public IP
# ICE_SERVERS= # Deprecated fallback for an external/3rd-party TURN; prefer TURN_AUTH_SECRET + TURN_URLS
# Cloudflare Tunnel (optional)
CLOUDFLARE_TUNNEL_TOKEN=
# GIF search (optional)
GIPHY_API_KEY=
Log Levels
The RUST_LOG variable controls logging verbosity using tracing-subscriber filter syntax:
# Production (default)
RUST_LOG=chatalot_server=info,tower_http=info
# Debug all server logs
RUST_LOG=chatalot_server=debug,tower_http=debug
# Debug specific modules
RUST_LOG=chatalot_server::ws=debug,chatalot_server::routes::auth=debug
# Verbose everything (generates a lot of output)
RUST_LOG=debug
After changing RUST_LOG, restart the container:
CORS Configuration
CORS is configured permissively in the server (allow_origin: Any, allow_methods: Any, allow_headers: Any). This is intentional because:
- The Tauri desktop client makes cross-origin requests from a
file://ortauri://origin - All API endpoints (except
/api/healthand/api/auth/*) require a valid JWT, which is the actual access gate
If you need to restrict CORS for your deployment, this would require modifying the source code in crates/chatalot-server/src/routes/mod.rs.
Security Headers
The server automatically sets the following security headers on all responses:
| Header | Value |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
X-XSS-Protection |
1; mode=block |
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
Referrer-Policy |
strict-origin-when-cross-origin |
Permissions-Policy |
camera=(self), microphone=(self), geolocation=() |
Content-Security-Policy |
Restricts scripts, styles, connections, and media sources |
Rate Limiting
The server includes built-in rate limiting:
| Scope | Rate | Burst |
|---|---|---|
| General API | 20 requests/second per IP | 50 |
| Auth endpoints (login, register) | 5 requests/second per IP | 10 |
Rate limited requests receive HTTP 429 with a JSON error body.
Next Step
For database-specific configuration, see Database Setup.