Setup
Advanced configuration
Every environment variable of the core and client stacks, with defaults and when to override them.
The three required values (TYTO_VERSION, SERVER_DOMAIN,
CORS_ALLOW_ORIGIN) are covered in Configuration.
This page is the full reference: every variable both stacks read, its
default, and when to touch it. The same list ships annotated as
.env.prod.example in each repo — that file is authoritative for your
pinned version.
Core stack
Required
TYTO_VERSIONImage tag to run — pin a release (never latest in production). One value pins core and, in the bundle shape, the client image with it.
SERVER_DOMAINPublic domain of the core API. Drives auto-HTTPS certificate issuance and every derived public URL below.
CORS_ALLOW_ORIGINOrigins of your web clients — space- or comma-separated exact origins, or a ^-anchored regex. Empty allows no cross-origin browser clients; native desktop/mobile clients don’t use CORS and are unaffected.
Reverse proxy & ports
BEHIND_PROXY boolean default falseSet true behind an existing reverse proxy: the stack serves plain HTTP, issues no certificates, and TRUSTED_PROXIES defaults to private ranges. Combine with HTTP_PORT.
HTTP_PORT / HTTPS_PORT default 80 / 443Host ports the stack binds. Change when 80/443 are taken — e.g. HTTP_PORT=8080 with your proxy pointing there.
TRUSTED_PROXIESCIDRs of trusted reverse proxies. Empty when FrankenPHP terminates TLS itself; with BEHIND_PROXY=true defaults to private ranges — set your proxy’s CIDR to narrow it.
TRUSTED_HOSTS default emptyHost-header allowlist regex. Empty by default — any Host is accepted. Recommended to set in production; ^${SERVER_DOMAIN}$ is the suggested value.
CADDY_GLOBAL_OPTIONSRaw Caddy global options — e.g. email you@example.com to attach an ACME contact for certificate-expiry notices. Certificates issue fine without it.
Bundled client
APP_DOMAINBundle shape only (compose.bundle.yaml): public domain of the bundled web client, served by this stack’s Caddy. Leave empty when the client runs as its own stack.
Public URLs & real-time
DEFAULT_URI / SERVER_API_URL derived from SERVER_DOMAINSet explicitly only when the public URLs differ from the hostname — typically behind a proxy that serves the stack on a different scheme or port than SERVER_DOMAIN implies.
MERCURE_CORS_ORIGINS default
CORS for the embedded Mercure hub (SSE). Subscriptions are authorized by JWT, not origin, so
Features
API_DOCS_ENABLED boolean default trueInteractive API reference (Swagger UI) at /api. false disables the docs UI; the API itself is unaffected.
APP_FALLBACK_LOCALE default enFallback for server-rendered content (welcome messages, push notifications, emails) when a community or user locale has no translation catalog.
VOICE_ENABLED boolean default truefalse runs a text-only server — hides all voice UI and refuses voice operations server-side. Use it on 443-only hosts where LiveKit’s UDP ports can’t be opened.
MAILER_DSNOptional env-level fallback transport only — real SMTP lives in the admin panel (stored in the DB, password encrypted). Empty means mail is discarded until SMTP is configured in the panel.
APP_SMTP_KEY derived from APP_SECRETEncryption key for the DB-stored SMTP password. Set a stable 32-byte hex value (openssl rand -hex 32) so the stored secret survives an APP_SECRET rotation.
Data services
DATABASE_URL derivedFull DSN override for an external/managed database. Empty derives mysql://tyto:<generated>@database:3306/tyto.
MARIADB_PASSWORD / MARIADB_ROOT_PASSWORD / MEILI_MASTER_KEY auto-generatedGenerated into the shared secrets volume on first boot. A non-empty value here always overrides the generated one — the path for managed services.
CACHE_DSN default redis://redis:6379Redis cache pool (redis:// scheme). Defaults to the bundled sidecar.
Voice (LiveKit)
LIVEKIT_PUBLIC_URL derived from SERVER_DOMAINThe signal WebSocket is proxied by the app on /rtc, so this derives automatically. Set it only for an external LiveKit with its own TLS. Voice media needs 7881/tcp + 50000–50100/udp open — see Voice setup.
LIVEKIT_INTERNAL_URL default http://livekit:7880Where the app reaches LiveKit’s API inside the compose network.
LIVEKIT_API_KEY / LIVEKIT_API_SECRET auto-generatedOverride only for an external LiveKit deployment.
Web push
VAPID_SUBJECTContact URI stamped into Web Push authentication, e.g. mailto:admin@example.com. The VAPID key pair itself is auto-generated.
Auto-generated app secrets
APP_SECRET, JWT_PASSPHRASE, MERCURE_JWT_SECRET,
MERCURE_SUBSCRIBER_JWT_KEY, APP_MEDIA_SIGNING_KEY, VAPID_PUBLIC_KEY,
VAPID_PRIVATE_KEY — all generated into the secrets volume on first boot
by the secrets-init service. Override in .env only if you must pin them
across hosts.
The secrets volume is state: losing it invalidates sessions (JWT keys), signed media URLs and push subscriptions, and can orphan the encrypted SMTP password. Include it in backups.
Client stack
Only for the separate-host shape — the bundle serves the client from the
core stack and needs none of these beyond APP_DOMAIN above.
TYTO_VERSIONClient image tag — keep it on the same release as the backend.
SERVER_DOMAINPublic domain serving the SPA. Auto-HTTPS unless BEHIND_PROXY=true.
API_DOMAINDomain of your core API (its SERVER_DOMAIN). The SPA discovers everything else from https://<API_DOMAIN>/api/server-info.
SERVER_INFO_URLAdvanced override: full server-info URL for a custom scheme or port. Takes precedence over API_DOMAIN.
BEHIND_PROXY / HTTP_PORT / HTTPS_PORT / CADDY_GLOBAL_OPTIONSSame semantics as the core stack’s variables above.
Everything not listed here — branding, registration, legal documents, rate limits, retention, upload limits, SMTP, bots — lives in the admin panel’s settings registry, not in env.