# Matrix stack (Synapse + Synapse-Admin + Element Web) behind Traefik A self-hosted Matrix homeserver, its admin panel, and a web client, all running in Docker, fronted by Traefik with automatic Let's Encrypt certificates. No container publishes a port to the host except Traefik (80/443). All persistent data lives in plain host folders under this project (no named Docker volumes), and the real domain name and secrets live only in a local, git-ignored `.env` file. ## Architecture ``` Internet ──80/443──> Traefik ──(docker network, no published ports)──┬──> Synapse (matrix.DOMAIN) ├──> Element Web (element.DOMAIN) ├──> Synapse-Admin (admin.DOMAIN) ├──> nginx: /.well-known/matrix/client (DOMAIN) └──> Traefik dashboard (traefik.DOMAIN, basic auth) Synapse ──> PostgreSQL (internal network only) ``` Matrix `server_name` is the bare domain (`DOMAIN`), so user IDs look like `@alice:DOMAIN`. The homeserver's actual HTTP API is served from `matrix.DOMAIN`, discovered by clients via `.well-known/matrix/client` delegation served from the bare domain. **Federation is intentionally disabled** (private/closed server) — see `synapse/data/homeserver.yaml`. Traefik uses its static **file provider** for routing (not the Docker provider / `docker.sock`), so Traefik never needs access to the Docker socket — a smaller attack surface, and it sidesteps Docker-API-version compatibility issues some environments have with Traefik's Docker provider. ## Prerequisites - Docker Engine + Docker Compose v2 - A domain name with DNS **A/AAAA records pointing at this server** for: - `DOMAIN` (e.g. `example.com`) - `matrix.DOMAIN` - `element.DOMAIN` - `admin.DOMAIN` - `traefik.DOMAIN` (subdomain names are configurable in `.env`) ## Setup 1. Copy the environment template and fill in your real domain/secrets: ```bash cp .env.example .env ``` Edit `.env`: - `DOMAIN` and the four `*_SUBDOMAIN` values - `ACME_EMAIL` — a real address you control (used by Let's Encrypt) - `TRAEFIK_DASHBOARD_AUTH` — generate with `openssl passwd -apr1` - `POSTGRES_PASSWORD` — generate with `openssl rand -hex 24` - `REGISTRATION_SHARED_SECRET` — generate with `openssl rand -hex 32` (this one gets baked into `homeserver.yaml` in step 3 below; keep both in sync if you change it later) 2. Render the templated config files (Element, Synapse-Admin, the well-known JSON, and Traefik's static routes) with your real domain: ```bash ./scripts/render-templates.sh ``` Re-run this any time you change domain-related values in `.env`. 3. Generate the initial Synapse config (first time only): ```bash docker run --rm \ -e SYNAPSE_SERVER_NAME= \ -e SYNAPSE_REPORT_STATS=no \ -v "$(pwd)/synapse/data:/data" \ matrixdotorg/synapse:latest generate ``` Then edit `synapse/data/homeserver.yaml`: - Remove `federation` from the listener's `resources` (already disabled by `federation_domain_whitelist: []`, this drops the endpoint too) - Replace the `database:` block with a `psycopg2` block pointing at the `postgres` service, using the credentials from `.env` - Add `public_baseurl: "https://matrix./"` - Add `federation_domain_whitelist: []` and `enable_registration: false` (This repo's own deployment already has this file populated under `synapse/data/` locally — it's git-ignored, so a fresh clone always starts from scratch here.) 4. Fix data directory ownership so the containers can write to their bind-mounted folders: ```bash chown -R 70:70 postgres/data # postgres container's uid/gid touch traefik/acme/acme.json && chmod 600 traefik/acme/acme.json ``` 5. Start everything: ```bash docker compose up -d ``` Watch `docker compose logs -f traefik` until certificates are issued. 6. Create your first (admin) user: ```bash docker compose exec synapse register_new_matrix_user \ -c /data/homeserver.yaml http://localhost:8008 -a ``` ## Notes - **Federation is off.** This is a private server for your own users only. To enable it, remove `federation_domain_whitelist: []` (or populate it), add `federation` back to the listener's `resources`, and expose the federation port through a Traefik router (typically still 443 via `.well-known/matrix/server` delegation, or a dedicated `8448` entrypoint). - **Registration is closed.** New accounts are created via `register_new_matrix_user` (or through Synapse-Admin once logged in as an admin), not via public sign-up in Element. - **Traefik dashboard** is reachable at `https://traefik.DOMAIN`, behind HTTP basic auth (`TRAEFIK_DASHBOARD_AUTH` in `.env`). - All data directories (`postgres/data`, `synapse/data`) are plain host folders, not Docker named volumes — back them up like any other files.