A ready-to-use configuration for deploying a Matrix homeserver via Docker Compose.
Also available in: Русский
- Matrix Synapse: The main homeserver, developed by Element Creations Ltd in Python.
- Caddy: Reverse proxy server, issues and manages Let's Encrypt certificates. Developed by Matt Holt in Go.
- LiveKit: Media server for MatrixRTC, written in Go by LiveKit Inc.
- lk-jwt-service: Authorization service for MatrixRTC, developed by Element Creations Ltd in Go.
- Prometheus: Metrics collection service, developed by Cloud Native Computing Foundation in Go.
- Grafana OSS: Observability and data visualization platform, developed by Grafana Labs in Go.
- Node Exporter: System metrics exporter, developed by Cloud Native Computing Foundation in Go.
.
├── caddy/ # Caddy configuration (Caddyfile)
├── livekit/ # LiveKit configuration example (livekit.yaml.example)
├── prometheus/ # Prometheus configuration (prometheus.yml)
├── static/ # Static files for the placeholder page (html, css, js)
├── synapse/ # Synapse with S3 support (Dockerfile)
├── .env.example # Environment variables example
└── compose.yaml # Main compose file
- Linux server (Debian 13 recommended)
- A domain name
- Open ports:
80/tcp443/tcp443/udp7881/tcp(optional)50100-50101/udp(optional)
The list of required ports depends on your MatrixRTC (LiveKit) configuration.
| Resource | Recommendation |
|---|---|
| CPU | 2-4 vCPU |
| RAM | 2-8 GB |
| Disk | 20+ GB |
| Network | Public IPv4 |
At least 4 GB RAM is recommended for MatrixRTC.
Create A records for your Matrix homeserver and MatrixRTC domains pointing to your server's IP address.
example.com A 203.0.113.1
matrix.example.com A 203.0.113.1
livekit.example.com A 203.0.113.1
grafana.example.com A 203.0.113.1
All services run on the same server, so all DNS records should point to the same IP address.
- Copy and fill in the environment variables:
cp .env.example .env-
Read the Components section and configure each service according to the documentation.
-
Start:
docker compose up -dMake sure your DNS records are configured and ports are open before starting — Caddy won't issue certificates without external access.
Matrix homeserver based on the official matrixdotorg/synapse image with the synapse-s3-storage-provider module added for storing media in S3-compatible storage.
Configuration files, keys, and media are stored in /var/lib/matrix-compose/data.
Create the directory:
mkdir -p /var/lib/matrix-compose/dataGenerate configuration files:
docker run -it --rm \
--mount type=bind,src=/var/lib/matrix-compose/data,dst=/data \
-e SYNAPSE_SERVER_NAME=example.com \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
Add the following line to homeserver.yaml:
public_baseurl: "https://matrix.example.com/"You also need to configure the Postgres connection:
database:
name: psycopg2
args:
user: synapse_user
password: your_strong_password
dbname: synapse
host: 1.2.3.4
port: 5432
cp_min: 5
cp_max: 10
keepalives_idle: 10
keepalives_interval: 10
keepalives_count: 3To enable MatrixRTC calls, you need to enable federation or openid:
listeners:
- port: 8008
resources:
- compress: false
names:
- client
# - federation
- openid # <---
tls: false
type: http
x_forwarded: trueYou also need to enable MSCs (Matrix spec proposals) and specify the LiveKit server domain:
experimental_features:
msc4222_enabled: true
msc4354_enabled: true
max_event_delay_duration: 24h
rc_message:
per_second: 0.5
burst_count: 30
rc_delayed_event_mgmt:
per_second: 1
burst_count: 20
matrix_rtc:
transports:
- type: livekit
livekit_service_url: https://livekit.example.com/livekit/jwtTo enable S3 storage, configure the storage provider:
media_storage_providers:
- module: s3_storage_provider.S3StorageProviderBackend
store_local: True
store_remote: True
store_synchronous: True
config:
bucket: <S3_BUCKET_NAME>
region_name: <S3_REGION_NAME>
endpoint_url: <S3_LIKE_SERVICE_ENDPOINT_URL>
access_key_id: <S3_ACCESS_KEY_ID>
secret_access_key: <S3_SECRET_ACCESS_KEY>
session_token: <S3_SESSION_TOKEN>See also:
Reverse proxy based on the official caddy image. Issues and manages TLS certificates for each subdomain, serves .well-known endpoints for Matrix, redirects all other requests to example.com, and serves static files.
By default, Caddy does not set a limit on request body size. An explicit limit of 300MB is configured:
request_body {
max_size 300MB
}The limit must match the limit in homeserver.yaml:
max_upload_size: 300MFor QUIC (HTTP/3) and LiveKit WebRTC traffic to work correctly, you need to increase the UDP buffers at the kernel level.
Apply immediately (until reboot):
sudo sysctl -w net.core.rmem_max=7500000
sudo sysctl -w net.core.wmem_max=7500000To persist after reboot, add to /etc/sysctl.conf:
net.core.rmem_max=7500000
net.core.wmem_max=7500000The value 7500000 is recommended by the quic-go library used internally by Caddy.
See also:
Media server for MatrixRTC. LiveKit does not support environment variables for most configuration parameters, so all settings are defined directly in livekit/livekit.yaml. Copy the example and fill it in:
cp livekit/livekit.yaml.example livekit/livekit.yamlInstead of opening dozens of ports, UDP mux is used. The number of ports must be no less than the number of CPU cores on the server:
rtc:
udp_port: 50100-50101You also need to expose these ports in compose.yaml:
livekit:
ports:
- "50100-50101:50100-50101/udp"If you use multiple LiveKit servers, Redis is required.
See also:
Authorization service for MatrixRTC based on the official image. Acts as middleware between the client and LiveKit: issues JWT tokens that authorize participation in a call. All parameters are passed via environment variables in .env — no separate configuration file is required.
Monitoring stack based on official images: Prometheus collects metrics from all services, node-exporter — host metrics, Grafana — visualization.
To enable Synapse metrics, add to homeserver.yaml:
enable_metrics: true
listeners:
# beginning of the new metrics listener
- port: 9000
type: metrics
bind_addresses: ['0.0.0.0']To enable LiveKit metrics, add to livekit/livekit.yaml:
prometheus_port: 6789Caddy exposes metrics on port 2019 — this is already configured in Caddyfile and prometheus/prometheus.yml, no additional steps required.
Grafana is available via Caddy with basic_auth. To generate a password hash (argon2id):
docker compose exec -it caddy sh
caddy hash-password --algorithm argon2id --plaintext 'yourpassword'See also:
Use GitHub Actions for automated deployment. Copy .github/workflows/deploy.yml into your repository — deployment will trigger automatically on a v*.*.* tag push or manually.
- Create a user on the server
sudo useradd -m -s /bin/bash gh-deploy
sudo mkdir -p /home/gh-deploy/.ssh
sudo chmod 700 /home/gh-deploy/.ssh- Create the deploy script
Create the script and make it executable:
sudo touch /path/to/deploy.sh
sudo chmod +x /path/to/deploy.shExample script:
#!/bin/bash
set -e
cd /path/to/matrix-compose
git pull origin main --ff-only
docker compose pull
docker compose up -d- Grant sudo privileges
echo "gh-deploy ALL=(root) NOPASSWD: /path/to/deploy.sh" | sudo tee /etc/sudoers.d/gh-deploy- Configure SSH access
Generate an SSH key for GitHub Actions and add the public key to /home/gh-deploy/.ssh/authorized_keys with a forced script execution on connection:
command="sudo /path/to/deploy.sh",no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA...
sudo chmod 600 /home/gh-deploy/.ssh/authorized_keys
sudo chown -R gh-deploy:gh-deploy /home/gh-deploy/.ssh- Add secrets to the repository
Settings → Secrets and variables → Actions → New repository secret
Get the server's public key for SSH_KNOWN_HOSTS:
ssh-keyscan -t ed25519 example.com| Secret | Description |
|---|---|
SSH_PRIVATE_KEY |
Private SSH key for connecting to the server |
SSH_KNOWN_HOSTS |
Server public key (output of ssh-keyscan) |
SSH_USER |
User on the server (gh-deploy) |
SSH_HOST |
Server IP address or domain |