feat: Sample Matrix swarm deployment compose file (#387)
All checks were successful
ci/woodpecker/push/infra Pipeline was successful

Standalone Synapse + Element Web deployment for Docker Swarm/Portainer.
Separate infrastructure from Mosaic Stack (same pattern as Authentik).

Includes: Synapse, Element Web, dedicated PostgreSQL, optional coturn.
Traefik labels match existing Stack conventions.

Refs #387

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 01:12:41 -06:00
parent d2003a7b03
commit 6e20fc5d16

View File

@@ -0,0 +1,206 @@
# ==============================================
# Matrix (Synapse + Element) - Sample Swarm Deployment
# ==============================================
#
# Standalone Matrix homeserver deployment for use with Mosaic Stack.
# This is SEPARATE infrastructure — not part of the Mosaic Stack itself.
# Mosaic connects to it via MATRIX_HOMESERVER_URL environment variable.
#
# Also serves: personal communications, GoToSocial bridges, other projects.
#
# Usage (Docker Swarm via Portainer):
# 1. Create a new stack in Portainer
# 2. Paste this file or point to the repo
# 3. Set environment variables in Portainer's env var section
# 4. Deploy the stack
#
# Usage (Docker Swarm CLI):
# 1. cp docker-compose.sample.matrix.env .env
# 2. nano .env # Configure
# 3. docker stack deploy -c docker-compose.sample.matrix.yml matrix
#
# Post-Deploy Setup:
# 1. Generate Synapse config (first run only):
# docker exec <synapse_container> python -m synapse.app.homeserver \
# --server-name ${MATRIX_DOMAIN} --report-stats no \
# --generate-config --config-path /data/homeserver.yaml
#
# 2. Create admin account:
# docker exec -it <synapse_container> register_new_matrix_user \
# -u admin -a -c /data/homeserver.yaml http://localhost:8008
#
# 3. Create Mosaic bot account:
# docker exec -it <synapse_container> register_new_matrix_user \
# -u mosaic-bot -c /data/homeserver.yaml http://localhost:8008
#
# 4. Generate bot access token:
# curl -X POST http://localhost:8008/_matrix/client/v3/login \
# -d '{"type":"m.login.password","user":"mosaic-bot","password":"<password>"}'
#
# 5. Set MATRIX_ACCESS_TOKEN in Mosaic Stack .env
#
# Required Environment Variables:
# MATRIX_DOMAIN=matrix.example.com # Synapse server name (permanent!)
# ELEMENT_DOMAIN=chat.example.com # Element Web domain
# POSTGRES_PASSWORD=<strong-password> # Synapse database password
#
# Optional Environment Variables:
# SYNAPSE_IMAGE_TAG=latest # Synapse version
# ELEMENT_IMAGE_TAG=latest # Element Web version
# POSTGRES_IMAGE_TAG=16-alpine # PostgreSQL version
# TRAEFIK_ENTRYPOINT=websecure # Traefik entrypoint name
# TRAEFIK_CERTRESOLVER=letsencrypt # Traefik cert resolver
# TRAEFIK_DOCKER_NETWORK=traefik-public # Traefik network name
# SYNAPSE_ENABLE_REGISTRATION=false # Public registration
# SYNAPSE_REPORT_STATS=no # Anonymous stats reporting
# SYNAPSE_MAX_UPLOAD_SIZE=50M # Max file upload size
#
# Connecting to Mosaic Stack:
# Add to your Mosaic Stack .env:
# MATRIX_HOMESERVER_URL=http://synapse:8008 (if same Docker network)
# MATRIX_HOMESERVER_URL=https://matrix.example.com (if external)
# MATRIX_ACCESS_TOKEN=<bot access token from step 4>
# MATRIX_BOT_USER_ID=@mosaic-bot:matrix.example.com
#
# ==============================================
services:
# ======================
# Synapse (Matrix Homeserver)
# ======================
synapse:
image: matrixdotorg/synapse:${SYNAPSE_IMAGE_TAG:-latest}
environment:
SYNAPSE_SERVER_NAME: ${MATRIX_DOMAIN}
SYNAPSE_REPORT_STATS: ${SYNAPSE_REPORT_STATS:-no}
SYNAPSE_CONFIG_DIR: /data
SYNAPSE_DATA_DIR: /data
SYNAPSE_LOG_LEVEL: ${SYNAPSE_LOG_LEVEL:-WARNING}
# Database connection (external PostgreSQL or bundled)
POSTGRES_HOST: synapse-postgres
POSTGRES_PORT: 5432
POSTGRES_DB: ${SYNAPSE_POSTGRES_DB:-synapse}
POSTGRES_USER: ${SYNAPSE_POSTGRES_USER:-synapse}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- synapse-data:/data
- synapse-media:/data/media_store
depends_on:
synapse-postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- internal
- traefik-public
deploy:
restart_policy:
condition: on-failure
delay: 10s
labels:
- "traefik.enable=true"
- "traefik.http.routers.matrix.rule=Host(`${MATRIX_DOMAIN}`)"
- "traefik.http.routers.matrix.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
- "traefik.http.routers.matrix.tls=${TRAEFIK_TLS_ENABLED:-true}"
- "traefik.http.routers.matrix.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
- "traefik.http.services.matrix.loadbalancer.server.port=8008"
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
# Well-known delegation (optional — for .well-known/matrix/server)
# - "traefik.http.routers.matrix-wellknown.rule=Host(`${MATRIX_DOMAIN}`) && PathPrefix(`/.well-known/matrix`)"
# ======================
# Element Web (Matrix Client)
# ======================
element-web:
image: vectorim/element-web:${ELEMENT_IMAGE_TAG:-latest}
volumes:
- element-config:/app/config
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
networks:
- internal
- traefik-public
deploy:
restart_policy:
condition: on-failure
labels:
- "traefik.enable=true"
- "traefik.http.routers.element.rule=Host(`${ELEMENT_DOMAIN}`)"
- "traefik.http.routers.element.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
- "traefik.http.routers.element.tls=${TRAEFIK_TLS_ENABLED:-true}"
- "traefik.http.routers.element.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}"
- "traefik.http.services.element.loadbalancer.server.port=80"
- "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}"
# ======================
# PostgreSQL (Synapse Database)
# ======================
# Separate from Mosaic's PostgreSQL — Synapse manages its own schema.
# If you prefer a shared PostgreSQL instance, remove this service and
# point POSTGRES_HOST to your existing PostgreSQL with a separate database.
synapse-postgres:
image: postgres:${POSTGRES_IMAGE_TAG:-16-alpine}
environment:
POSTGRES_DB: ${SYNAPSE_POSTGRES_DB:-synapse}
POSTGRES_USER: ${SYNAPSE_POSTGRES_USER:-synapse}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
volumes:
- synapse-postgres-data:/var/lib/postgresql/data
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${SYNAPSE_POSTGRES_USER:-synapse} -d ${SYNAPSE_POSTGRES_DB:-synapse}",
]
interval: 10s
timeout: 5s
retries: 5
networks:
- internal
deploy:
restart_policy:
condition: on-failure
# ======================
# coturn (TURN/STUN for VoIP) - Optional
# ======================
# Uncomment if you need voice/video calls through NAT.
# Requires additional DNS and port configuration.
#
# coturn:
# image: coturn/coturn:latest
# environment:
# TURN_REALM: ${MATRIX_DOMAIN}
# TURN_SECRET: ${COTURN_SECRET}
# ports:
# - "3478:3478/tcp"
# - "3478:3478/udp"
# - "5349:5349/tcp"
# - "5349:5349/udp"
# - "49152-49200:49152-49200/udp"
# networks:
# - internal
# deploy:
# restart_policy:
# condition: on-failure
volumes:
synapse-data:
synapse-media:
synapse-postgres-data:
element-config:
networks:
internal:
driver: overlay
traefik-public:
external: true
name: ${TRAEFIK_DOCKER_NETWORK:-traefik-public}