Skip to content

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

  1. In Authentik, create a new OAuth2/OpenID Provider:
  2. Name: chatalot
  3. Authorization flow: implicit consent (or explicit, depending on your preference)
  4. Redirect URI: https://chat.example.com/auth/oidc/callback
  5. Scopes: openid, email, profile

  6. Create an Application linked to the provider, with the slug chatalot.

  7. Set the environment variables:

    OIDC_ISSUER_URL=https://auth.example.com/application/o/chatalot/
    OIDC_CLIENT_ID=<client-id-from-authentik>
    OIDC_CLIENT_SECRET=<client-secret-from-authentik>
    OIDC_REDIRECT_URI=https://chat.example.com/auth/oidc/callback
    

  8. Restart Chatalot:

    docker compose up -d
    

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, and profile scopes 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:

npx web-push generate-vapid-keys

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.

  1. Set the required environment variables in .env:

    TURN_AUTH_SECRET=$(openssl rand -hex 32)
    TURN_URLS=turns:turn.example.com:5349,turn:turn.example.com:3478
    TURN_EXTERNAL_IP=203.0.113.50  # Your server's public IP
    

  2. Start Chatalot with the turn profile:

    docker compose --profile turn up -d
    

  3. Ensure the following ports are open in your firewall (swap in your TURN_LISTENING_PORT/TURN_TLS_PORT values if you overrode the defaults):

  4. 3478/tcp and 3478/udp -- plaintext STUN/TURN
  5. 5349/tcp -- TLS-over-TURN
  6. 5349/udp -- DTLS-over-TURN
  7. 49152-49200/udp -- Media relay range

  8. First boot generates a self-signed cert in the turn_certs named volume. To rotate, docker volume rm chatalot_turn_certs and 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:

TURN_LISTENING_PORT=3479
TURN_TLS_PORT=5350

Then update three things to match:

  1. TURN_URLS — the ports your clients connect to:
    TURN_URLS=turns:turn.example.com:5350,turn:turn.example.com:3479
    
  2. Firewall rules — open the new ports, close the old ones if they're no longer needed.
  3. 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:

docker compose up -d

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:// or tauri:// origin
  • All API endpoints (except /api/health and /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.