Matrix (Synapse)
- Category: Apps
- Status: 0, work-in-progress
- Image:
matrixdotorg/synapse, 4, upstream - Healthcheck: Yes
- Backups: No
- Email: Yes
- Tests: No
- SSO: Yes
Basic usage
- Set up Docker Swarm and
abra - Deploy
coop-cloud/traefik abra app new matrix-synapse --secrets(optionally with--passif you'd like to save secrets inpass)abra app config YOURAPPDOMAIN- be sure to change$DOMAINto something that resolves to your Docker swarm boxabra app deploy YOURAPPDOMAIN- Create an initial user:
abra app run YOURAPPDOMAIN app register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008
Tips & Tricks
Create User
register_new_matrix_user -u <username> -k $(cat /var/run/secrets/registration) -p <password>
Set Admin User
abra app cmd YOURAPPDOMAIN db set_admin <adminuser>
Disabling federation
- Use
DISABLE_FEDERATION=1to turn off federation listeners - Don't use
compose.matrix.ymlin your traefik config to keep the federation ports closed
Enabling federation
Federation is on by default (DISABLE_FEDERATION=0). Remote homeservers need a way to discover the host:port that serves your SERVER_NAME. There are three supported approaches.
Option 1: built-in well-known (SERVER_NAME = DOMAIN)
Set SERVE_SERVER_WELLKNOWN=true and leave SERVER_NAME unset (defaults to DOMAIN). The recipe's nginx serves /.well-known/matrix/server and /.well-known/matrix/client on DOMAIN.
Suitable when users are e.g. @alice:matrix.example.com.
Option 2: external well-known on SERVER_NAME
Use when you want users to be e.g. @alice:example.com while Synapse runs at matrix.example.com. Set:
SERVER_NAME=example.com
DOMAIN=matrix.example.com
SERVE_SERVER_WELLKNOWN=false
The two paths that must be served on SERVER_NAME are:
https://example.com/.well-known/matrix/server→{"m.server": "matrix.example.com:443"}https://example.com/.well-known/matrix/client→{"m.homeserver": {"base_url": "https://matrix.example.com"}}
Recommended — let this recipe serve them via Traefik by enabling compose.wellknown.yml:
COMPOSE_FILE="$COMPOSE_FILE:compose.wellknown.yml"
This publishes a Traefik router Host(${SERVER_NAME}) && PathPrefix(/.well-known/matrix)
pointing at the matrix nginx, which already serves both files. The path-scoped, high-priority
rule coexists with any apex website that also serves Host(${SERVER_NAME}) — that site keeps
serving everything except /.well-known/matrix. SERVER_NAME must resolve to this Traefik so
ACME can issue its certificate.
Alternative — serve the two files yourself from whatever already hosts example.com.
Option 3: Traefik matrix-federation entrypoint (port 8448)
Use when SERVER_NAME ≠ DOMAIN but you have no separate web service at SERVER_NAME. Remote homeservers fall back to SERVER_NAME:8448 when there's no delegation.
Requirements:
- traefik
>= 5.1.2+v3.6.15withMATRIX_FEDERATION_ENABLED=1andcompose.matrix.ymlenabled. SERVER_NAMEset in your matrix-synapse env (used by the federation router's Host rule).
With these in place, the recipe publishes a Traefik router on Host(${SERVER_NAME}) via the matrix-federation entrypoint, reusing the existing matrix nginx → synapse path.
Option 4: DNS SRV records (usually not viable here)
Federation can also be delegated with a DNS SRV record on SERVER_NAME instead of well-known:
_matrix-fed._tcp.example.com. 3600 IN SRV 10 0 8448 matrix.example.com. # modern
_matrix._tcp.example.com. 3600 IN SRV 10 0 8448 matrix.example.com. # deprecated, for older peers
The catch is TLS: on the SRV path a remote validates the certificate against SERVER_NAME, not the SRV target. This recipe's Traefik only issues a cert for DOMAIN, so:
- SRV →
DOMAIN:443 fails — the presented cert is forDOMAIN, but the peer requires one forSERVER_NAME. - SRV →
SERVER_NAME:443 collides — Traefik routes TLS by SNI, andHost(SERVER_NAME)on:443is already owned by whatever apex site servesSERVER_NAME. - SRV →
SERVER_NAME:8448 works — the Option 3matrix-federationrouter holds a cert forSERVER_NAME— but that's just Option 3 made explicit (the:8448fallback already works with no SRV record).
So I think SRV does little here. Probably prefer Option 2 (well-known).
Verifying
The canonical test:
Or check the underlying paths directly. They should all return JSON:
# Options 1 & 2 — delegation
curl https://SERVER_NAME/.well-known/matrix/server
# Option 3 — federation endpoint via 8448
curl https://SERVER_NAME:8448/_matrix/key/v2/server
# Confirms Synapse itself is healthy (independent of the path remote servers use)
curl https://DOMAIN/_matrix/key/v2/server
Getting client discovery on a custom domain
Enable compose.wellknown.yml (see Option 2 above) — it serves /.well-known/matrix/client
on SERVER_NAME too, so clients signing in as @alice:example.com auto-discover the homeserver.
Bridges
For all Bridges:
- Setting it up is a bit of a chicken/egg & chasing cats moment.
- Make sure to uncomment
APP_SERVICES_ENABLED,HOMESERVER_URL,HOMESERVER_DOMAIN,compose.shared_secret_auth.yml,SHARED_SECRET_AUTH_ENABLEDandSECRET_SHARED_SECRET_AUTH_VERSION - include the registration in synapse, e.g.
APP_SERVICE_CONFIGS="[\"/telegram-data/registration.yaml\"]" - and set yourself as admin, e.g.:
TELEGRAM_BRIDGE_PERMISSIONS="{ \"*\": \"relaybot\", \"@akadmin:example.com\": \"admin\"}"
Important
The shared secret authenticator may break when matrix-synapse uses a newer python version with an error stating something like "module not found". You have to fix the path in the compose.shared_secret_auth.yml like here
Telegram bridging
You need to get your bot setup on the telegram side first by creating a telegram app and a telegram bot and have these values:
api_id: ...
api_hash: ...
telegram_bot_token: ...
Experimental script for a automated token replacement:
DOMAIN=<domain>
abra app secret insert $DOMAIN telegram_api_hash v1 <secret>
abra app secret insert $DOMAIN telegram_bot_token v1 <secret>
abra app secret generate -a $DOMAIN
abra app deploy $DOMAIN
abra app cmd -l $DOMAIN set_bridge_tokens telegram
Alternatively a manual guide for the necessary steps:
DOMAIN=<domain>
abra app secret insert $DOMAIN telegram_api_hash v1 <secret>
abra app secret insert $DOMAIN telegram_bot_token v1 <secret>
abra app secret generate -a $DOMAIN
abra app deploy $DOMAIN
abra app run $DOMAIN telegrambridge cat /data/registration.yaml
abra app undeploy $DOMAIN
abra app secret rm $DOMAIN telegram_as_token
abra app secret insert $DOMAIN telegram_as_token v1 <secret>
abra app secret rm $DOMAIN telegram_hs_token
abra app secret insert $DOMAIN telegram_hs_token v1 <secret>
abra app deploy $DOMAIN
Some helpful documentation:
Discord bridging
WIP docs
Just as messy as the Telegram bridging above! Rough guide:
- get a local copy of
config.yaml - fill it out with the values you need, all the discord token stuff, etc.
- run
mkdir -p data && cp config.yaml data/thendocker run --rm -v data:/data halfshot/matrix-appservice-discord:v1.0.0 sh -c "cd /data && node /build/src/discordas.js -r -u "http://discordbridge:9005" -c config.yaml" - this generates the app service registration configuration you need to feed to the homeserver
- run secret generation for the
discord_db_password, insert yourdiscord_bot_token - run
abra app cp <domain> discord-registration.yaml app:/discord-data(it has to be calleddiscord-registration.yaml) - deploy the bridge & happy hacking
Some helpful documentation:
Signal bridging
Experimental script for a more automated token replacement:
DOMAIN=<domain>
abra app secret generate -a $DOMAIN
abra app deploy $DOMAIN
abra app cmd -l $DOMAIN set_bridge_tokens signal
Alternatively a manual guide for the necessary steps:
DOMAIN=<domain>
abra app secret insert $DOMAIN signal_hs_token v1 foo
abra app secret insert $DOMAIN signal_as_token v1 foo
abra app secret generate $DOMAIN -a
abra app deploy $DOMAIN
abra app run $DOMAIN signalbridge cat /data/registration.yaml
abra app secret rm $DOMAIN signal_as_token
abra app secret insert $DOMAIN signal_as_token v1 <secret>
abra app secret rm $DOMAIN signal_hs_token
abra app secret insert $DOMAIN signal_hs_token v1 <secret>
abra app deploy $DOMAIN
- message
@signalbot:example.comto test - See the docs for authentication