Repository navigation
Configuration and 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-eindocker 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 fordocker compose build. Outside Docker they'reVITE_*variables fornpm run buildinweb/.
For installation itself, see the README.
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| 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). |
| 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.
|
| 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.
|
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-secretwith astatic-auth-secret, and set the same value asTURN_SECREThere. -
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. |
| 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. |
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.
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. |
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/.
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.
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).