Skip to content

Configuration reference

  • 1 file .env, next to docker-compose.yml
  • 1 command docker compose up -d applies a change
  • 600 the mode to keep .env at

Everything lives in one file, .env, next to docker-compose.yml. firstboot.sh writes it for you; this page is for changing it afterwards.

  • Apply a change with docker compose up -d. It recreates only the containers whose settings changed.
  • Anything marked Never share is a secret. Keep .env at mode 600, keep it out of tickets and chat, and do not paste it into a support request.
  • A variable with no default listed is off or empty until you set it.
Variable What it does Default Change it when
PUBLIC_HOST The hostname this install answers on. The gateway gets a Let’s Encrypt certificate for it on first boot. Needs a DNS record and ports 80 and 443 reachable. More than one name is allowed as a comma-separated list, but then the value must be quoted. unset (serves localhost on an internal certificate) You go live.
PUBLIC_BASE_URL Reachable base URL of the API (avatar URLs, auth callbacks). With PUBLIC_HOST set, use https:// plus that host. http://127.0.0.1:3001 You go live.
APP_URL Where users actually reach the app. Drives auth and invite redirects and CORS. The server refuses to boot in production if this is localhost. http://127.0.0.1:3001 You go live.
CORS_ORIGIN Origins allowed to call the API, comma-separated. APP_URL The desktop app signs in but cannot join a call. Its renderer has its own origin, and a missing entry fails silently on the server side.
NODE_IP Address clients use to reach this host for media. Must be routable from the client. 127.0.0.1 Always, for anything but a single-machine test. Set it to the host’s LAN or public IP, or calls negotiate and carry no audio.
LIVEKIT_PUBLIC_URL How clients reach the media server. ws:// plus NODE_IP and LK_PORT_HTTP You go live: wss://your-host.
SERVER_PORT Host port for the API. 3001 A port clash.
GATEWAY_PORT Host port for the gateway. 8000 A port clash.
LK_PORT_HTTP, LK_PORT_TCP, LK_PORT_UDP Host ports for the media server. 7880, 7881, 7882 A port clash.
LK_PORT_TURN Embedded TURN port (UDP). The same port is used inside and out. 3478 A port clash.
LK_TURN_RELAY_START, LK_TURN_RELAY_END Published TURN relay range (UDP). Must match turn.relay_range_start and turn.relay_range_end in livekit.yaml; widen both together or neither. 30000 to 30063 You need more relay capacity.
HTTP_PORT, HTTPS_PORT The public edge. Change only to sit behind something else that owns 80 and 443. TLS then becomes that thing’s job, because the Let’s Encrypt challenge is served on port 80. 80, 443 Almost never.
AUTH_PUBLIC_URL Browser-facing address for the sign-in redirect hop. The public gateway, never the internal one. PUBLIC_BASE_URL Almost never.
TEAMHUD_VERSION Image tag for the server, game server, recorder and draft service. stable You pin a version, or you are on edge before the first stable release.
TEAMHUD_IMAGE, TEAMHUD_GAME_IMAGE, TEAMHUD_RECORDER_IMAGE, TEAMHUD_DRAFT_IMAGE Image repositories. ghcr.io/arimus/teamhud-server, -game-server, -recorder, -draft-asr You mirror the images.
LIVEKIT_IMAGE, EGRESS_IMAGE Media server and egress images. livekit/livekit-server:v1.13.5, livekit/egress:v1.10.0 You run a patched build.
GIT_COMMIT Build identifier reported by the server. deploy.sh sets it from the checked-out commit. unknown Never by hand.
MAILER_AUTOCONFIRM Confirm users on sign-up. Use true for a closed deployment with no outbound mail. true You want email confirmation.
TRUST_PROXY_HOST The peer the server trusts for a real X-Forwarded-For: Caddy, by compose service name. gateway You should not need to.
TRUST_PROXY Explicit IP or CIDR allowlist of trusted proxies. Wins over TRUST_PROXY_HOST. unset Something else, such as a load balancer, sits in front of Caddy. Caddy then also needs trusted_proxies.
TRUST_PROXY_HOPS Retired. A hop count is no longer honoured (it let anyone forge X-Forwarded-For). Setting it only logs a warning. none Remove it if you set it.
RATE_LIMIT_STORE Where rate limits and sign-in lockouts are counted: memory or postgres. memory You run more than one server. Set postgres on every one, or each counts only its own traffic. The 600-requests-a-minute flood guard always counts in memory.
CALL_DIAG_TOKEN Retired. One shared token could not say who read a call’s diagnostics. A System admin now mints a token per person or tool (POST /api/admin/call-diagnostics/tokens with a label and their password; shown once, expires, revocable), and every read is recorded. Setting it only logs a warning. none Remove it if you set it.
CALL_DIAG_READS_RETENTION_MONTHS Months to keep the record of who read a call’s diagnostics, when, and from where (never the diagnostics themselves). Older rows are deleted automatically each day by the server. Whole number from 3 to 120; anything lower stops the server from starting. 12 Set the same value on every instance.

Generated by node gen-keys.mjs >> .env. All are Never share.

Variable What it does Default Change it when
POSTGRES_PASSWORD Database password. Also the way into the database from the box’s shell. generated Rarely.
JWT_SECRET Signs sign-in tokens. generated Regenerate with ANON_KEY and SERVICE_ROLE_KEY.
ANON_KEY, SERVICE_ROLE_KEY Tokens derived from JWT_SECRET. The service-role key is full database access. generated Regenerate together with JWT_SECRET.
LIVEKIT_API_KEY, LIVEKIT_API_SECRET Credentials between the server and the media server. generated Rotate together.
ENCRYPTION_KEY Encrypts per-org credentials at rest (an org’s own transcription key, its own media provider). Exactly 64 hex characters. Without it, those routes fail with “ENCRYPTION_KEY is not configured” while the rest of the stack looks healthy. generated Never casually. Changing it makes stored per-org credentials unreadable.
AUTH_DB_PASSWORD, STORAGE_DB_PASSWORD, TEAMHUD_APP_DB_PASSWORD Passwords of the database roles used by sign-in, file storage and the server. generated Rarely.
REDIS_PASSWORD Password of the Redis that egress uses. generated Rarely.
DRAFTS_API_KEY The key the server, recorder and draft service share for live caption drafts. Never the same as RECORDER_TOKEN; at least 16 characters. generated Rotate together on all three.
RECORDER_TOKEN Shared between the server, recorder and draft service. Without it, recordings capture nothing and the UI says so. generated Rotate together on all three.
GAME_SERVER_SECRET Signs mini-game join tokens. Only with the game server. generated by firstboot.sh if you opt in You rotate it.
ENT_SECRET Signs entitlements and licences. Required for the Slack doorbell: without it every subscribe is rejected with no_ent_secret and clients fall back to polling. Any long random string. unset You use Slack or Teams live updates.
Variable What it does Default Change it when
SIGNUP_MODE Who may create an account: open, sso_only or closed (invite-only: only someone invited by email can create an account). See Users, roles and sign-up. Apply with docker compose up -d, not deploy.sh, which recreates only the server and leaves GoTrue’s own sign-up unchanged. open You want to restrict sign-up.
DISABLE_SIGNUP Legacy. true behaves as SIGNUP_MODE=closed (invite-only). firstboot.sh writes true unless you choose open sign-up. If both are set, SIGNUP_MODE wins. false Prefer SIGNUP_MODE.
GOTRUE_DISABLE_SIGNUP GoTrue’s own sign-up flag. Follows DISABLE_SIGNUP when unset. follows DISABLE_SIGNUP On an SSO-only box set it false, because GoTrue has to be able to create a user on the sign-in callback. This is not an open door: the public site forwards only GoTrue’s browser hops. Set true for a fully closed box.
BOOTSTRAP_ADMIN_EMAIL Email of the first System admin. Acts only while the platform has no admin; the server creates the account and prints its password once. unset You want the first System admin created at boot instead of through the setup link.
SETUP_TOKEN One-time token for /setup. Until the first team exists the server answers nothing else without it: no sign-up, sign-in, call or invite (403 setup_required). Inert after. Never share. generated by firstboot.sh You install by hand and want the setup link.
TEAMHUD_REQUIRE_SETUP A self-hosted server with no team yet and no SETUP_TOKEN stays shut (it logs how to unlock it at boot). false turns off only that case; a set SETUP_TOKEN still locks. Leave it on. true Only a disposable test stack that starts empty with no token (set false).
ADMIN_IP_ALLOWLIST IPs and IPv4 CIDRs, comma-separated, allowed to open /admin and /api/admin/*. Fails closed: unset means the admin pages answer 404 to everyone. unset Always set it.
MIN_CLIENT_VERSION Oldest desktop version accepted without an “Update TeamHUD” nudge. Soft: an older client is flagged at sign-in, never refused. unset (no floor) You want everyone on a minimum version.
AUTH_THROTTLE_EXEMPT_IPS Exact client addresses (comma-separated, no ranges) that the sign-in, sign-up and password-reset failure throttles never count or refuse. Meant for a test runner. unset Never for real users. Do not point it at a range real users share.
RATE_LIMIT_GLOBAL_MAX Global requests per client per minute. 600 Test stacks only.
RESEND_API_KEY, RESEND_FROM Invite emails through Resend. Without a key the invitation and its shareable link are still created; only the email is skipped. Supabase still sends auth mail. RESEND_API_KEY is Never share. unset You want invite emails.

See Slack and Microsoft for the full app setup.

Variable What it does Default Change it when
SOCIAL_PROVIDERS Social sign-in offered to clients, for example slack_oidc. A provider also has to be switched on in GoTrue, or it shows no button. empty (none) You turn on Slack or Microsoft sign-in.
CHANNEL_PROVIDERS Lets orgs use Slack’s own channels and DMs (slack). Needs slack_oidc in SOCIAL_PROVIDERS. teams is accepted but ignored until Teams channels are built. empty (TeamHUD channels only) Same.
GOTRUE_EXTERNAL_SLACK_OIDC_ENABLED Switches Slack sign-in on in GoTrue. false You use Slack sign-in.
GOTRUE_EXTERNAL_SLACK_OIDC_CLIENT_ID Slack app client ID. unset Same.
GOTRUE_EXTERNAL_SLACK_OIDC_SECRET Slack app secret. Never share. unset Same.
GOTRUE_EXTERNAL_SLACK_OIDC_REDIRECT_URI Sign-in callback. PUBLIC_BASE_URL plus /auth/v1/callback Set only to override.
GOTRUE_EXTERNAL_AZURE_ENABLED Switches Microsoft sign-in on in GoTrue. false You use Microsoft sign-in.
GOTRUE_EXTERNAL_AZURE_CLIENT_ID Entra app registration client ID. unset Same.
GOTRUE_EXTERNAL_AZURE_SECRET Entra app secret. Never share. unset Same.
AZURE_TENANT Pins sign-in to your Entra tenant (id or domain). Unset means common, which lets any work, school or personal Microsoft account sign in. common Recommended for a private server.
GOTRUE_EXTERNAL_AZURE_URL Full authority URL. Overrides AZURE_TENANT. No /v2.0 suffix. https://login.microsoftonline.com/ plus the tenant Advanced.
GOTRUE_EXTERNAL_AZURE_REDIRECT_URI Sign-in callback. PUBLIC_BASE_URL plus /auth/v1/callback Set only to override.
GOTRUE_URI_ALLOW_LIST Where GoTrue may send someone after a provider redirect. If a target is missing, GoTrue discards redirect_to and bounces to the site root, with no error. this deployment plus the desktop app’s loopback Sign-in never completes after the provider step.
SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, SLACK_SIGNING_SECRET Slack app for channels and the doorbell. The secret and signing secret are Never share. unset You use Slack channels.
SLACK_EVENT_SOURCE events needs Slack to reach this box. socket needs no inbound URL but rings channel doorbells only, not DMs. off You use the Slack doorbell. Prefer events.
SLACK_APP_TOKEN Socket Mode app token (xapp-). Never share. unset Only with socket.
SLACK_APP_AUDIENCE_TOKEN App-level token with authorizations:read. Without it a group DM rings only one participant. Never share. unset You use the doorbell.
SLACK_REDIRECT_URI Slack OAuth callback override. PUBLIC_BASE_URL plus /slack/oauth/callback Slack reaches this box on a different hostname, such as a tunnel.
MSTEAMS_TENANT_ID, MSTEAMS_CLIENT_ID, MSTEAMS_CLIENT_SECRET Separate Entra registration for live Teams updates. Needs ENT_SECRET and admin consent. The secret is Never share. unset You want live Teams updates. See Microsoft.
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GMAIL_PUSH_TOPIC, GMAIL_PUSH_SA_EMAIL, GOOGLE_REDIRECT_URI Google and Gmail push. The secret is Never share. unset You use Gmail. See Google.

The default is local disk under a Docker volume, which is right when the reason you self-host is that data should not leave your building.

Variable What it does Default Change it when
STORAGE_BACKEND s3 moves attachments to S3-compatible object storage. file You prefer object storage.
STORAGE_WORKSPACE Root of every object key. Write-once: changing it later orphans everything already uploaded, silently. stub Set it once when you switch to s3.
STORAGE_PREFIX Optional level above the workspace, for one bucket holding several deployments. unset Rarely.
STORAGE_S3_REGION, STORAGE_S3_BUCKET Bucket location. stub You switch to s3.
STORAGE_S3_ENDPOINT The regional endpoint with no bucket in the hostname. unset You switch to s3.
STORAGE_S3_ACCESS_KEY, STORAGE_S3_SECRET_KEY Bucket credentials. Never share. unset You switch to s3.
STORAGE_S3_PROTOCOL, STORAGE_S3_FORCE_PATH_STYLE Connection style. https, true Rarely.
STORAGE_FILE_SIZE_LIMIT Largest object the storage API accepts, in bytes. storage-api’s own default is 50 MB, which silently breaks the update feed. 2147483648 (2 GiB) Rarely.
Variable What it does Default Change it when
TEAMHUD_LICENSE_KEY, TEAMHUD_LICENSE_PUBLIC_KEY The licence you were issued and the key that proves we issued it. Both are needed. Verification fails closed and logs nothing, so a missing key runs the trial or free ladder and looks healthy. Check GET /usage, not the logs. The licence key is Never share. unset You hold a licence.
TEAMHUD_REQUIRE_ACTIVATION A self-hosted server that was never activated (no key, pasted or from TEAMHUD_LICENSE_KEY) serves only the activation screen: sign in to TeamHUD Cloud and approve the code against one of your subscriptions, or paste a key. A subscription covers one server plus a backup; a third server on the same key is flagged to you, not blocked. Applies only when TEAMHUD_SELF_HOSTED=true, which the shipped compose sets for you, so a stock self-host is gated with no edits. A server that was activated and later lapses keeps running at Free limits. Leave it on. true Only a test or dev stack with no key (set false).
TEAMHUD_LICENSE_SERVER Where the daily renewal check goes. https://dashboard.teamhud.app Only if you run your own issuer.
TEAMHUD_SELF_HOSTED Declares whose hardware this is. The shipped compose file defaults it to true, so on your own box you change nothing. false is the metered cloud free tier and also switches the activation requirement off. TeamHUD’s own hosted and dev boxes must set TEAMHUD_SELF_HOSTED=false in their .env before deploying this version, or they are gated. true in the compose file (false when the server runs outside compose) Never false on your own box.
TEAMHUD_AMBIENT_ROOM_CAP People allowed in one always-on team room. Takes effect only where TEAMHUD_SELF_HOSTED=true. 15 Your hardware and bandwidth can take more. Above about 30, a meeting fits better.
SELF_HOST_BUNDLE_PATH Storage path of the bundle GET /downloads/server serves. Unset: 404. unset Only a server that hands out the server bundle.
UPDATE_AUTOPIN_ENABLED Pins a rollout back when its cohort’s crash or update-failed rate crosses the threshold. false You distribute installers.
UPDATE_AUTOPIN_THRESHOLD, UPDATE_AUTOPIN_MIN_SAMPLE, UPDATE_AUTOPIN_WINDOW_MINUTES Failure rate, minimum beacons, and window. Raise the sample with ENABLED. 0.2, 20, 360 Tuning.
TEAMHUD_TERMS_URL, TEAMHUD_PRIVACY_URL, TEAMHUD_ACCEPTABLE_USE_URL Your own legal documents. Unset, the server declares documents on its own origin (/legal/terms), so users never consent to TeamHUD’s document into your database. unset You have your own terms.
TEAMHUD_LEGAL_VERSION Bump when you amend your terms. It forces every user to re-accept. unset You change your terms.
TEAMHUD_LEGAL_GRACE_DAYS Days a user may keep going before accepting, counted from the first time the server tells that user to accept. After that the account gets 403 terms_required. 0 means accept before using it at all. 14 Tighten or relax.
TEAMHUD_LEGAL_EFFECTIVE_AT Optional ISO date before which no grace period starts. unset You announce terms ahead of time.
Variable What it does Default Change it when
ENABLE_GAMES, COMPOSE_PROFILES Team mini-games. firstboot.sh asks (default yes). On yes it adds games to COMPOSE_PROFILES and generates GAME_SERVER_SECRET. true after firstboot.sh; the profile is off if you never ran it You want mini-games.
GAME_SERVER_URL Where the server proxies /game/*. Compose-internal. http://game-server:2567 Never.
TURN_TLS_ENABLED, TURN_DOMAIN, TURN_RELAY_START, TURN_RELAY_END TURN over TLS on port 443, for clients behind firewalls that allow only outbound 443. Needs a public NODE_IP, free host ports 80 and 443, and a DNS record for TURN_DOMAIN (unless it equals PUBLIC_HOST). firstboot.sh turns the compose override on through COMPOSE_FILE. off; relay 30000 to 30199 Clients on 443-only networks. LAN or VPN-only orgs leave it off.
FCM_PROJECT_ID, FCM_CLIENT_EMAIL, FCM_PRIVATE_KEY Push notifications to phones, from a Firebase service-account JSON. Quote the key and escape newlines as \n. The key is Never share. unset (push off) You ship a mobile client.

These reach the server and recorder through docker-compose.yml. They only matter when an Org owner or Org admin has switched capture on; see Org policies.

Variable What it does Default Change it when
TRANSCRIPTION_PROVIDER Deployment-default provider: deepinfra or openai-compatible. Unset means each org configures its own. unset You want one provider for everyone.
DEEPINFRA_API_KEY Key for deepinfra. Never share. unset Same.
TRANSCRIPTION_BASE_URL, TRANSCRIPTION_MODEL, TRANSCRIPTION_API_KEY For openai-compatible, such as a local Speaches or NeMo-Speech server. The key is Never share. unset Same.
DRAFTS_URL Live word-by-word caption drafts service. An empty value turns drafts off, and captions then arrive per sentence. http://draft-asr:3003 Move the load to another box, or turn drafts off.
DRAFT_WORKERS, DRAFT_MAX_STREAMS Draft service workers and concurrent speaker streams. 2, 16 CPU tuning.
DRAFT_FINAL_MODEL, DRAFT_FINAL_WORKERS, DRAFT_FINAL_THREADS, DRAFT_FINAL_QUEUE Second-pass local model. off means drafts only. parakeet-v2, 1, unset, 4 CPU tuning.
DIARIZER_URL Optional speaker-splitting service for meeting-room mics. Unset means one speaker per mic. unset You run one.
EGRESS_OUTPUT_DIR Enables audio and video files of record. Set /recordings with COMPOSE_PROFILES=egress and LIVEKIT_REDIS_ADDRESS=redis:6379. empty (no media files; transcription still works) You want saved media.
LIVEKIT_REDIS_ADDRESS Needed by egress. empty (single node) With egress.
LIVEKIT_URL, LIVEKIT_WEBHOOK_URL Internal media-server and webhook addresses. An empty webhook disables it. ws://livekit:7880, http://server:3001/livekit/webhook Never on the stock stack.
TEAMHUD_ANALYTICS_URL, SFU_METRICS_TOKEN Media-server metrics. Same-host only; a self-hosted box phones nothing home. The token is Never share. unset Leave unset.

A server with no internet uses an offline key: download it from your TeamHUD Cloud subscription (paid subscriptions only), typing the workspace name it is for, and paste it in Settings → License or set TEAMHUD_LICENSE_KEY before first start. Create your first workspace with that exact name. The key is valid to the end of your subscription term (up to one year), never checks in, and after it expires the server keeps running at Free limits following a 30-day grace period. Because offline keys never check in, the two-servers-per-key notice cannot apply to them.

ALLOW_E2E_DEV_MEETINGS, ALLOW_E2E_LOOPBACK_CORS and the E2E_CLOUDFLARE_* variables exist for the project’s own test stack. Leave them unset on a real install.