Fully dockerized deployment with automatic Let's Encrypt certificates via Traefik's static file provider (no docker.sock needed), bind-mounted data folders instead of named volumes, and domain/secrets kept out of git via .env + rendered templates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
119 lines
5.1 KiB
Markdown
119 lines
5.1 KiB
Markdown
# 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=<your DOMAIN> \
|
|
-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.<DOMAIN>/"`
|
|
- 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.
|