Setup

Getting started

Install and run Tyto in production with Docker Compose — separate stacks or the single-stack bundle.

Tyto ships as two independently deployable parts: core (the API server — Symfony, MariaDB, Meilisearch, Valkey, LiveKit, all one compose stack) and client (the web app — static files behind Caddy). Three deployment shapes:

  • Core alone — an API server for native/desktop clients, or the first half of a two-host setup.
  • Core + separate client — each part on its own host (or the same host behind a reverse proxy).
  • Single-stack bundle — one host, one stack: core’s Caddy also serves the web client on its own domain. The simplest full setup.

The condensed version of this page lives on Quick Start.

Prerequisites

  • A server with Docker and Docker Compose (Linux recommended)
  • Ports 80/443 open and a DNS record per domain — API (e.g. chat.example.com) and, if you deploy the web client, app (e.g. app.example.com)
  • Voice channels: 7881/tcp + 50000–50100/udp open directly on the core host — WebRTC media never goes through a proxy.
Note

Can’t open the voice media ports? Set VOICE_ENABLED=false for a text-only server — all voice UI disappears.

HTTPS is automatic: the images ship with Caddy and provision certificates via ACME when SERVER_DOMAIN is a real hostname. If ports 80/443 already belong to a reverse proxy, see Behind a reverse proxy.

Backend (core)

mkdir tyto-core && cd tyto-core
curl -LO https://raw.githubusercontent.com/tyto-chat/core/master/compose.yaml

Create .env — the required configuration is three values:

TYTO_VERSION=1.0.0
SERVER_DOMAIN=chat.example.com
CORS_ALLOW_ORIGIN=https://app.example.com

TYTO_VERSION pins the release you run (the current release is 1.0.0-beta1) — it selects the tag of the prebuilt ghcr.io/tyto-chat/tyto-core image. Pinning keeps deploys reproducible and makes upgrades deliberate; unset it and Compose falls back to latest, which silently jumps versions on every pull. Pin it in production.

SERVER_DOMAIN is the public hostname the API is served on — Caddy uses it as the site address and provisions its TLS certificate for it.

CORS_ALLOW_ORIGIN lists the origins of your web clients — space- or comma-separated, several if you run more than one. It only concerns browsers: native desktop or mobile clients connect without CORS, and if you run no web client at all it can stay empty.

Everything else is derived or generated on first boot:

  • Secrets — database passwords, the Meilisearch master key, the LiveKit API key/secret and all app keys (JWT, Mercure, media signing, VAPID) are auto-generated into the shared secrets volume by a one-shot secrets-init service. Setting any of them in .env overrides the generated value (managed database, external LiveKit).
  • URLsDEFAULT_URI, SERVER_API_URL, LIVEKIT_PUBLIC_URL and the Mercure public URL derive from SERVER_DOMAIN — including in reverse-proxy mode, where the domain stays real. Set them explicitly only when public URLs differ from the hostname.
  • Voice signal — rides the API origin (/rtc), so it shares the API’s TLS; only the media ports from the prerequisites are extra.
  • Mail — configured in the admin panel after first login (stored encrypted in the DB); MAILER_DSN is only an optional env fallback.
  • CADDY_GLOBAL_OPTIONS may carry email you@example.com to attach an ACME contact to the Let’s Encrypt account — optional.

Every other variable is documented inline in .env.prod.example.

docker compose up -d
docker compose exec app php bin/console tyto:user:create --admin

The first start pulls the images, runs database migrations and generates keys. The app service serves the API; worker and scheduler handle async jobs (notifications, digests, retention purges). MariaDB, Valkey, Meilisearch and LiveKit run alongside as services of the same compose file.

Client (separate stack)

mkdir tyto-client && cd tyto-client
curl -LO https://raw.githubusercontent.com/tyto-chat/client/master/compose.yaml

Create the client’s .env — three values (all of them are described in .env.prod.example):

TYTO_VERSION=1.0.0
SERVER_DOMAIN=app.example.com
API_DOMAIN=chat.example.com

API_DOMAIN points the app at your core API — the backend’s SERVER_DOMAIN. The SPA reaches it at https://<API_DOMAIN>/api/server-info; for a custom scheme or port set the full SERVER_INFO_URL instead. Keep TYTO_VERSION on the same release as the backend.

docker compose up -d

The client is configured at runtime — the same image works against any backend. Change API_DOMAIN and up -d to repoint it; no rebuild.

Single-stack bundle

One host, both parts, no separate client stack: the client image only contributes its built files, served by the core stack’s Caddy on their own domain (certificates for both are provisioned by the same Caddy).

In the core directory:

curl -LO https://raw.githubusercontent.com/tyto-chat/core/master/compose.bundle.yaml

Add to the core .env:

APP_DOMAIN=app.example.com

and make sure CORS_ALLOW_ORIGIN allows https://app.example.com. Then:

docker compose -f compose.yaml -f compose.bundle.yaml up -d

Both DNS records point at this one host. Upgrades and every other command work the same — just always pass both -f files.

First run

Open the app domain, sign in with the admin account — the in-app setup wizard walks you through SMTP, bot and realtime configuration — and create your first community.

Upgrades

# edit .env: bump TYTO_VERSION to the new release
docker compose pull && docker compose up -d

Migrations run automatically on start; workers restart gracefully. Pinned versions make rollbacks equally boring: set the previous tag and repeat. Details — what runs on update, rollback caveats, what to back up — in Updating.

Development setup

Both repos ship a one-command DDEV environment for contributors — see the READMEs of core and client.