A self-hosted Matrix server stack with modern OIDC authentication, web clients, optional video calling, messaging bridges, and observability.
Run NixOS? See nixmatrix — the same stack as a single declarative NixOS flake (deploy to a fresh VPS with
nixos-anywhere). This Docker Compose project is the right choice for running on an existing Docker host; nixmatrix is the right choice if you prefer NixOS.
- Synapse — Matrix homeserver
- Matrix Authentication Service (MAS) — OIDC-based authentication (replaces legacy password auth)
- Element Web — Web client
- Ketesa — Admin dashboard (user/room management via Synapse Admin API)
- synapse_auto_compressor — Continuous state-group compression to reduce DB bloat
- PostgreSQL 16 — Database
- Caddy — Reverse proxy with automatic HTTPS
- FluffyChat — Alternative Matrix web client with a friendly UI
- Also registers FluffyChat as an OIDC client in MAS, which fixes the cross-signing reset "continue" button
- LiveKit — WebRTC SFU media server (self-hosted, media stays on your server)
- lk-jwt-service — LiveKit token issuer
- Element Call — Self-hosted video/voice calling frontend
Always available (start with the stack, set up via setup-bridges.sh):
- mautrix-whatsapp — WhatsApp bridge (Go megabridge)
- mautrix-signal — Signal bridge (Go megabridge)
- mautrix-telegram — Telegram bridge (requires API credentials)
- mautrix-discord — Discord bridge (Go megabridge)
- mautrix-slack — Slack bridge (Go megabridge — Megaslack)
Optional (--profile hookshot):
- matrix-hookshot — GitHub, GitLab, JIRA, RSS, and generic webhook integrations
- Prometheus — Metrics collection (scrapes Synapse at
:9000/metrics) - Grafana — Dashboard visualization (Synapse dashboard pre-provisioned)
- Authelia — SSO / identity provider with 2FA (
--profile authelia) - Any OIDC-compliant provider — Authentik, Keycloak, Zitadel, etc. (no extra containers)
Simple production deployment — four prompts, everything else is automatic:
./quickstart.shAsks for: your domain, a Let's Encrypt email, whether to enable Element Call, and whether to allow open registration. Generates all secrets and configs, starts the stack. Monitoring is always enabled.
Advanced deployment — local testing, SSO options, multi-machine setups, optional bridges and monitoring:
./deploy.shBridges are set up separately after the core stack is running:
./setup-bridges.shBrowser
|
Caddy (HTTPS, Let's Encrypt)
|
+-- matrix.example.com --> Synapse :8008
| /.well-known --> (served inline by Caddy)
| /login, /logout --> MAS :8080
+-- auth.example.com --> MAS :8080
+-- element.example.com --> Element Web :80
+-- chat.example.com --> FluffyChat :80 (optional)
+-- admin.example.com --> Ketesa :8080
+-- monitoring.example.com --> Grafana :3000 (optional)
+-- call.example.com --> Element Call :8080 (optional)
+-- rtc.example.com --> lk-jwt-service :8080 (optional)
LiveKit :7880 (optional)
All services communicate over an internal Docker network. The database is not exposed.
Simple production — single machine, Let's Encrypt, four prompts:
./quickstart.shAdvanced deployment — three modes via interactive menu:
./deploy.sh| Mode | Description |
|---|---|
| Local testing | Self-signed certs, *.example.test domains, /etc/hosts entries |
| Production (single-server) | Let's Encrypt, all services on one machine |
| Production (distributed) | Caddy / Authelia / Matrix on separate hosts, generates caddy/Caddyfile.production |
SSO options (prompted during deploy.sh):
- None — MAS handles passwords directly
- Authelia — full SSO with 2FA via
--profile authelia - Other OIDC — Authentik, Keycloak, Zitadel, or any OIDC-compliant provider (no extra containers)
When enabled, all three components are self-hosted. Media streams never leave your server (they route through your LiveKit SFU). The Element Call frontend is served from your own call. subdomain.
Required open ports in addition to 80 and 443:
- TCP 7881 (WebRTC signaling)
- UDP 50100–50200 (media streams)
setup-bridges.sh configures WhatsApp, Signal, Discord, and Slack automatically. Telegram requires API credentials from my.telegram.org — add them to .env before running:
TELEGRAM_API_ID=your_id
TELEGRAM_API_HASH=your_hashAll bridges use Go megabridge implementations (Discord, Slack, WhatsApp, Signal) or the current Python bridge (Telegram). Bridges use double puppet support (messages appear from your actual Matrix user, not a bridge bot) and have encryption disabled for compatibility with MAS.
See BRIDGE_SETUP_GUIDE.md for details, including Discord, Slack, and matrix-hookshot setup.
When the monitoring profile is enabled, Prometheus scrapes Synapse metrics every 15 seconds and Grafana is provisioned with:
- A Prometheus datasource pointing to the internal
prometheus:9090endpoint - The official Synapse dashboard (ID 10046) downloaded at deploy time
Synapse exposes metrics at :9000/metrics via a separate metrics.yaml config file loaded alongside homeserver.yaml (Synapse directory config mode).
synapse_auto_compressor runs continuously alongside Postgres and compresses the state_groups_state table — the primary source of Synapse database bloat. It requires no configuration and runs automatically whenever Postgres is healthy.
Add Python packages to synapse/requirements.txt (one per line) to install them into the Synapse image at build time — useful for storage providers, LDAP modules, or custom auth handlers. An empty file is a no-op.
deploy.sh optionally prefixes all image references with a custom registry URL (for internal mirrors or air-gapped environments) and optionally switches Redis, PostgreSQL, and Caddy to hardened variants from dhi.io. Both settings are written to .env and picked up automatically by Docker Compose.
- Docker and Docker Compose v2
- A domain with DNS control
- Ports 80 and 443 accessible from the internet
- For Element Call: ports 7881/TCP and 50100–50200/UDP open
# Status
docker compose ps
# Logs
docker compose logs -f [service]
# Restart a service
docker compose restart synapse
# Update all images
docker compose pull && docker compose up -d
# Bridge logs
docker compose logs mautrix-whatsapp
docker compose logs mautrix-discord
# Monitoring
docker compose --profile monitoring logs -f prometheus
docker compose --profile monitoring logs -f grafanapostgres/data/ database (back this up)
synapse/data/ media store, signing keys
mas/data/ MAS sessions
.env all secrets and domain config
Backup:
tar -czf matrix-backup-$(date +%Y%m%d).tar.gz postgres/data synapse/data mas/data .env- SETUP.md — manual configuration reference
- PRODUCTION_DEPLOYMENT.md — production checklist and hardening
- BRIDGE_SETUP_GUIDE.md — bridge configuration (including Discord, Slack, hookshot)
- BUGFIXES.md — known issues and their solutions
- QUICK_REFERENCE.md — common commands
- Synapse: Apache 2.0
- Matrix Authentication Service: Apache 2.0
- Element Web / Element Call: Apache 2.0
- Ketesa: MIT
- FluffyChat: AGPL 3.0
- PostgreSQL: PostgreSQL License
- Caddy: Apache 2.0
- LiveKit: Apache 2.0
- Prometheus: Apache 2.0
- Grafana: AGPL 3.0