Skip to content

Configuration and Environment Variables

Moepchi edited this page Oct 7, 2026 · 5 revisions

Configuration & Environment Variables

Every setting a self-hosted WebSpeak3 instance understands. Everything is optional: docker run -p 8080:8080 moepchi/webspeak3 works with no configuration at all.

There are two kinds of settings:

  • Runtime variables are read by the gateway when it starts. Set them on the container (under environment: in Compose or with -e in docker run), then recreate the container. No rebuild needed.
  • Build-time variables are baked into the web client when it's built. With Docker they're --build-args for docker compose build. Outside Docker they're VITE_* variables for npm run build in web/.

For installation itself, see the README.

Example

services:
  webspeak3:
    image: moepchi/webspeak3:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - ALLOWED_SERVERS=voice.example.com,backup.example.com:9988
      - DEFAULT_SERVER=voice.example.com
      - DEFAULT_CHANNEL=Lobby
      - ALLOWED_ORIGINS=https://voice.example.com

Runtime variables (gateway)

Basics

Variable Default Purpose
PORT 8080 Port the gateway listens on.
WEB_STATIC on Set to 0 to stop serving the web client. The gateway then only answers /ws, /healthz and the /api/* endpoints. Use this when the client is hosted elsewhere (see VITE_GATEWAY_URL below).
WEB_DIST /app/web/dist (Docker) Directory the web client is served from. Only needed for non-standard layouts.
CONNECTOR_BIN /app/connector-bin/ts-connector (Docker) Path to the Rust connector binary that speaks the TeamSpeak protocol. Only needed for non-standard layouts.
TLS_CERT / TLS_KEY unset Certificate and key file paths, to serve HTTPS/WSS directly instead of behind a reverse proxy or tunnel. Set both. Browsers only allow microphone access over HTTPS (or on localhost).

Which servers visitors can use

Variable Default Purpose
DEFAULT_SERVER / DEFAULT_CHANNEL unset Server (and optionally channel) every visitor auto-connects to when they open the page. A ?connect=... link still takes precedence. If the connection fails, the connect dialog opens prefilled. Same as the build arguments of the same name, but these win and need no rebuild.
ALLOWED_SERVERS unset (any server) Comma-separated list of the only servers this gateway will connect to, e.g. ts.example.com,*.example.net. Any other server is refused before a connection is even attempted. Ports aren't checked: ts.example.com allows every port on that host. An entry may still include a port (ts.example.com:9988); that's the address the connect dialog offers (see SERVER_PICKER). *.example.com matches every subdomain, but not example.com itself, so list that separately if needed.
SERVER_PICKER on When ALLOWED_SERVERS is set, the connect dialog shows a dropdown of those servers instead of a free-text address field. With a single allowed server the field shows it but is locked. Set to 0 to keep the plain text field. Lists containing *. entries always keep the text field, since subdomains can't be listed. Only takes effect when the gateway serves the web client itself (as the Docker image does). Since v0.16.0-beta.2.
ALLOW_PRIVATE_TARGETS off By default the gateway refuses private/internal addresses (loopback, 10.x, 192.168.x, CGNAT, link-local, …), however they were reached (IP, DNS, SRV, TSDNS, nickname, file-transfer redirect). This stops it from being used to probe the network it runs in. If your TeamSpeak server is on the same LAN as the gateway, set this to 1.

Abuse protection

Variable Default Purpose
ALLOWED_ORIGINS unset (any origin) Comma-separated list of web origins allowed to open /ws, e.g. https://voice.example.com. Stops other websites from quietly using your gateway as their relay.
MAX_CONNECTIONS_PER_IP 10 Concurrent /ws connections per client IP. 0 disables the limit.
CONNECT_RATE_LIMIT 30 Server connects per client IP per minute. Each connect starts a new TeamSpeak session, so this keeps anyone from flooding a server through your gateway. 0 disables it.
MAX_CONNECTIONS 300 Concurrent /ws connections in total. 0 disables the limit.
TRUST_PROXY off Only when the gateway is reachable solely through a proxy or tunnel, so per-IP limits see the real client instead of the proxy. cloudflare (Cloudflare Tunnel or Cloudflare proxy in front) uses CF-Connecting-IP. 1 (your own nginx, Caddy, Traefik, …) uses the last X-Forwarded-For entry, the one your proxy added. Don't set it on a directly exposed gateway, since clients could then fake their IP. See Running behind a Reverse Proxy. cloudflare since v0.16.0-beta.3; before that, use 1.

Screen sharing (TURN)

Status: screen streaming is alpha. It only works with TeamSpeak 6 servers and native TS6 clients, using direct peer-to-peer connections. TS6's "Server" mode (SFU) isn't supported. See the roadmap.

TURN is optional. Without it, browsers only use STUN (TeamSpeak's and Google's public STUN servers). That's enough for most home connections, but not when either side is behind CGNAT, a symmetric NAT or a strict firewall; then the stream stays black. A TURN server relays the stream in those cases.

Where to get one:

  • Your own coturn (free, any small VPS): enable use-auth-secret with a static-auth-secret, and set the same value as TURN_SECRET here.
  • A hosted TURN service with fixed credentials: use TURN_USERNAME / TURN_CREDENTIAL.
  • Services that only hand out short-lived credentials through their own API (e.g. Cloudflare, Twilio) aren't supported yet.
Variable Default Purpose
TURN_URLS unset Comma-separated TURN URLs (e.g. turn:turn.example.com:3478) handed to browsers for screen streams. They help when viewers or streamers are behind NATs where a direct connection fails.
TURN_SECRET unset Shared secret for coturn's use-auth-secret. Each browser gets its own credential, valid for 24 hours. Preferred over static credentials.
TURN_USERNAME / TURN_CREDENTIAL unset Static TURN credentials, as an alternative to TURN_SECRET. Every visitor can read them.

Maintenance

Variable Default Purpose
BROADCAST_TOKEN unset Enables POST /api/broadcast, which shows a notice to every connected client (e.g. before a restart). Send Authorization: Bearer <token> and a JSON body {"message": "..."} (max. 500 characters). The endpoint returns 404 while this is unset.
LOG_CONNECTIONS off Set to 1 to log connects and disconnects to the container log: target host, server name and time only, no nicknames or IPs.
RUST_LOG warn Log level of the TeamSpeak connector, e.g. RUST_LOG=debug when troubleshooting a connection. Very noisy.

Design Store (your own catalog)

Only needed if you want to run your own Design Store instead of using the shared one. The client also has to point at it (STORE_URL build argument below).

Variable Default Purpose
STORE_ENABLED off Set to 1 to enable /api/store/themes on this gateway. Returns 404 while unset.
STORE_DATA_FILE store-themes.json in the working directory Where submitted designs (including screenshots) are stored. Mount a volume over it so they survive a container recreate.
STORE_ALLOWED_ORIGIN * CORS origin allowed to use the store endpoint.
STORE_ADMIN_TOKEN unset Moderation token. New submissions wait as "pending" until approved on /api/store/admin, where you enter the token. The token holder's own submissions skip the queue. Without it, nothing is ever published.
STORE_RESERVED_AUTHORS Möpchi,Moepchi Comma-separated author names only the STORE_ADMIN_TOKEN holder may publish under, so nobody can impersonate you. Set your own name(s) here.

Submitted CSS is checked on the server: no @import, no @font-face, no external url(), only inline data: images.

Feedback form

Off by default. The Feedback menu entry only appears on the official instance, but the endpoint can be enabled anywhere.

Variable Default Purpose
FEEDBACK_ENABLED off Set to 1 to accept submissions on /api/feedback.
FEEDBACK_LOG_FILE feedback.log in the working directory JSON-lines file submissions are appended to.
FEEDBACK_ALLOWED_ORIGIN * CORS origin allowed to submit feedback.
GITHUB_TOKEN unset Token with "Issues: write" access. When set, each submission also becomes a public GitHub issue. The contact e-mail is left out and only stays in the log file.
GITHUB_REPO Moepchi/webspeak3 Repository those issues are created in.

Build-time variables (web client)

Docker --build-arg Outside Docker (web/) Default Purpose
DEFAULT_SERVER / DEFAULT_CHANNEL VITE_DEFAULT_SERVER / VITE_DEFAULT_CHANNEL unset Server/channel to auto-connect to, baked into the client. The runtime variables of the same name win.
DONATE_URL VITE_DONATE_URL project's Ko-fi link The donate button. Empty (DONATE_URL=) removes it; any other value points it elsewhere.
STORE_URL VITE_STORE_URL shared catalog Design Store catalog the client uses, e.g. https://your-gateway.example.com/api/store/themes.
— VITE_GATEWAY_URL same host as the page Gateway address when the client is hosted somewhere else, e.g. wss://gateway.example.com (the /ws path is added automatically). Needs real TLS when the page is served over HTTPS.
— VITE_DEMO_MODE off true builds the offline demo with simulated data and no gateway (what the GitHub Pages demo uses).

Docker example: docker compose build --build-arg DEFAULT_SERVER=voice.example.com --build-arg DONATE_URL=

Outside Docker: VITE_DEFAULT_SERVER=voice.example.com VITE_DONATE_URL= npm run build in web/.

Repository docker-compose.yml

The docker-compose.yml in the repository also starts a cloudflared tunnel. It reads CLOUDFLARE_TUNNEL_TOKEN from a .env file next to it. That variable is for Compose, not WebSpeak3. If you don't use Cloudflare Tunnel, remove the cloudflared service and TRUST_PROXY=cloudflare, and publish a port instead.

Trust model

The gateway speaks the TeamSpeak protocol on the user's behalf, so whoever runs it handles their identity key, server/channel passwords and privilege keys. They're kept out of process listings, but the operator can still see them. Only use gateways you trust, and serve them over HTTPS/WSS (TLS_CERT/TLS_KEY or a TLS-terminating proxy).

Clone this wiki locally