Docker Deployment
Deploy a complete Matrix setup using Docker Compose.
Prerequisites
- Docker Engine 20.10+
- Docker Compose v2+
- Domain name with DNS configured
- Ports 80, 443, 8448 available
Quick Start
1. Create Directory Structure
mkdir -p matrix-server/{synapse-data,postgres-data}
cd matrix-server
2. Create Docker Compose File
docker-compose.yml
version: '3.8'
services:
synapse:
image: matrixdotorg/synapse:latest
container_name: synapse
restart: unless-stopped
environment:
- SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
volumes:
- ./synapse-data:/data
depends_on:
- postgres
ports:
- "8008:8008"
networks:
- matrix
postgres:
image: postgres:15-alpine
container_name: synapse-db
restart: unless-stopped
environment:
- POSTGRES_USER=synapse
- POSTGRES_PASSWORD=CHANGE_ME_TO_SECURE_PASSWORD
- POSTGRES_DB=synapse
- POSTGRES_INITDB_ARGS=--encoding=UTF8 --locale=C
volumes:
- ./postgres-data:/var/lib/postgresql/data
networks:
- matrix
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "8448:8448"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- matrix
networks:
matrix:
driver: bridge
3. Create Caddyfile
Caddyfile
matrix.example.com {
reverse_proxy /_matrix/* synapse:8008
reverse_proxy /_synapse/* synapse:8008
}
matrix.example.com:8448 {
reverse_proxy synapse:8008
}
example.com {
handle /.well-known/matrix/server {
respond `{"m.server": "matrix.example.com:443"}`
}
handle /.well-known/matrix/client {
header Access-Control-Allow-Origin *
respond `{"m.homeserver": {"base_url": "https://matrix.example.com"}}`
}
}
4. Generate Synapse Config
docker run -it --rm \
-v $(pwd)/synapse-data:/data \
-e SYNAPSE_SERVER_NAME=example.com \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
5. Configure Synapse
Edit synapse-data/homeserver.yaml:
homeserver.yaml
server_name: "example.com"
public_baseurl: "https://matrix.example.com/"
database:
name: psycopg2
args:
user: synapse
password: CHANGE_ME_TO_SECURE_PASSWORD
database: synapse
host: postgres
cp_min: 5
cp_max: 10
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
enable_registration: false
6. Start Services
docker compose up -d
7. Create Admin User
docker exec -it synapse register_new_matrix_user \
-c /data/homeserver.yaml \
-a -u admin http://localhost:8008
Adding Element Web
docker-compose.yml (add to services)
element:
image: vectorim/element-web:latest
container_name: element
restart: unless-stopped
volumes:
- ./element-config.json:/app/config.json
networks:
- matrix
element-config.json
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.com",
"server_name": "example.com"
}
},
"brand": "Element",
"default_theme": "dark"
}
Add to Caddyfile:
element.example.com {
reverse_proxy element:80
}
Adding Bridges
Discord Bridge
docker-compose.yml (add to services)
mautrix-discord:
image: dock.mau.dev/mautrix/discord:latest
container_name: mautrix-discord
restart: unless-stopped
volumes:
- ./discord-data:/data
networks:
- matrix
Telegram Bridge
docker-compose.yml (add to services)
mautrix-telegram:
image: dock.mau.dev/mautrix/telegram:latest
container_name: mautrix-telegram
restart: unless-stopped
volumes:
- ./telegram-data:/data
networks:
- matrix
Adding Coturn (VoIP)
docker-compose.yml (add to services)
coturn:
image: coturn/coturn:latest
container_name: coturn
restart: unless-stopped
network_mode: host
volumes:
- ./turnserver.conf:/etc/coturn/turnserver.conf
turnserver.conf
listening-port=3478
tls-listening-port=5349
external-ip=YOUR_SERVER_IP
realm=turn.example.com
server-name=turn.example.com
lt-cred-mech
use-auth-secret
static-auth-secret=YOUR_TURN_SECRET
Add to Synapse config:
turn_uris:
- "turn:turn.example.com:3478?transport=udp"
- "turn:turn.example.com:3478?transport=tcp"
turn_shared_secret: "YOUR_TURN_SECRET"
turn_user_lifetime: 86400000
Backup Script
backup.sh
#!/bin/bash
DATE=$(date +%Y%m%d)
BACKUP_DIR=/backups
# Stop Synapse for consistent backup
docker compose stop synapse
# Backup database
docker exec synapse-db pg_dump -U synapse synapse | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# Backup media
tar -czf $BACKUP_DIR/media_$DATE.tar.gz synapse-data/media_store
# Backup config
cp synapse-data/homeserver.yaml $BACKUP_DIR/homeserver_$DATE.yaml
cp synapse-data/*.signing.key $BACKUP_DIR/
# Start Synapse
docker compose start synapse
# Cleanup old backups (keep 7 days)
find $BACKUP_DIR -mtime +7 -delete
Monitoring
Add Prometheus monitoring:
docker-compose.yml (add to services)
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- matrix
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
networks:
- matrix
Enable metrics in Synapse:
enable_metrics: true
Updating
# Pull latest images
docker compose pull
# Restart with new images
docker compose up -d
Troubleshooting
Check Logs
docker compose logs -f synapse
docker compose logs -f postgres
docker compose logs -f caddy
Database Issues
docker exec -it synapse-db psql -U synapse synapse
Federation Test
curl https://federationtester.matrix.org/api/report?server_name=example.com
Next: Ansible Deployment →