Skip to content

Latest commit

 

History

History
401 lines (303 loc) · 12.5 KB

File metadata and controls

401 lines (303 loc) · 12.5 KB

Manual Configuration Reference

This document describes what each component needs and how the automated scripts configure it. Use this when you need to understand, modify, or troubleshoot the generated configuration.

For most users: run ./quickstart.sh (production) or ./deploy.sh (advanced). The scripts handle everything below automatically.


Configuration Files

.env

All secrets and domain names. Generated by quickstart.sh or deploy.sh. Do not commit to version control.

Key variables:

Variable Purpose
MATRIX_DOMAIN Synapse homeserver domain (e.g. matrix.example.com)
AUTH_DOMAIN MAS domain (e.g. auth.example.com)
ELEMENT_DOMAIN Element Web domain
ADMIN_DOMAIN Element Admin domain
POSTGRES_PASSWORD Shared database password (same across all services)
MAS_SECRET_KEY 64-char hex encryption key for MAS
LIVEKIT_SECRET LiveKit API secret (only if Element Call enabled)
POSTGRES_IMAGE PostgreSQL image (default: postgres:16-alpine)
SYNAPSE_IMAGE Synapse image (default: matrixdotorg/synapse:latest)
ELEMENT_IMAGE Element Web image (default: vectorim/element-web:latest)
ELEMENT_ADMIN_IMAGE Element Admin image (default: oci.element.io/element-admin:latest)
REDIS_IMAGE Redis image (default: redis:7-alpine)
MAS_IMAGE MAS image (default: ghcr.io/element-hq/matrix-authentication-service:latest)
TELEGRAM_IMAGE mautrix-telegram image (default: dock.mau.dev/mautrix/telegram:latest)
WHATSAPP_IMAGE mautrix-whatsapp image (default: dock.mau.dev/mautrix/whatsapp:latest)
SIGNAL_IMAGE mautrix-signal image (default: dock.mau.dev/mautrix/signal:latest)
LIVEKIT_IMAGE LiveKit image (default: livekit/livekit-server:latest)
LK_JWT_IMAGE lk-jwt-service image (default: ghcr.io/element-hq/lk-jwt-service:latest)
ELEMENT_CALL_IMAGE Element Call image (default: ghcr.io/element-hq/element-call:latest)
AUTHELIA_IMAGE Authelia image (default: authelia/authelia:latest)
CADDY_IMAGE Caddy image (default: caddy:2-alpine)

All image variables are optional. If absent, each compose service falls back to the default tag via ${VAR:-default} syntax, so docker compose works without a .env file.

synapse/data/homeserver.yaml

Generated by the official Synapse tool:

docker run --rm \
  -v "$(pwd)/synapse/data:/data" \
  -e SYNAPSE_SERVER_NAME=matrix.example.com \
  -e SYNAPSE_REPORT_STATS=no \
  matrixdotorg/synapse:latest generate

The scripts then patch in these sections:

database:
  name: psycopg2
  args:
    user: synapse
    password: YOUR_POSTGRES_PASSWORD
    database: synapse
    host: postgres
    port: 5432
    cp_min: 5
    cp_max: 10

enable_registration: false

matrix_authentication_service:
  enabled: true
  endpoint: 'http://mas:8080'
  secret: 'YOUR_SYNAPSE_SHARED_SECRET'

If Element Call is enabled, also:

experimental_features:
  msc3266_enabled: true
  msc4222_enabled: true
  msc4140_enabled: true

max_event_delay_duration: 24h

rc_delayed_event_mgmt:
  per_second: 1
  burst_count: 20

If bridges or double puppet are configured, also:

app_service_config_files:
  - /appservices/doublepuppet.yaml
  - /bridges/whatsapp/config/registration.yaml
  - /bridges/signal/config/registration.yaml
  - /bridges/telegram/config/registration.yaml

mas/config/config.yaml

MAS is configured with:

  • HTTP listener on [::]:8080 with resources: discovery, human, oauth, compat, graphql, assets
  • Internal health listener on 127.0.0.1:8081
  • PostgreSQL connection
  • RSA signing key (generated by openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt)
  • passwords.enabled: true (MAS handles auth directly; set to false if using Authelia)

MAS client registrations required:

Client ID Purpose
01HQW90Z35CMXFJWQPHC3BGZGQ Element Web (public client)
01ADMN00000000000000000000 Element Admin (public client)
0000000000000000000SYNAPSE Synapse backend (confidential, client_secret_basic)

The client ID 01ADMN00000000000000000000 is a valid 26-character Crockford base32 ULID. The previously used 01ADMIN000000000000000000 (25 chars, contains I) was invalid and caused YAML parse errors on startup.

Redirect URIs for Element Web:

https://element.yourdomain.com
https://element.yourdomain.com/mobile_guide/
io.element.app:/callback

Redirect URIs for Element Admin:

https://admin.yourdomain.com/
https://admin.yourdomain.com

element/config/config.json

Served inline by Caddy (not read from the container's filesystem). The Caddyfile intercepts /config.json requests and returns the JSON directly as a respond directive.

Minimum required structure:

{
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://matrix.example.com",
            "server_name": "matrix.example.com"
        }
    },
    "default_server_name": "matrix.example.com",
    "features": {
        "feature_oidc_aware_navigation": true
    }
}

With Element Call:

{
    "features": {
        "feature_oidc_aware_navigation": true,
        "feature_element_call_video_rooms": true
    },
    "element_call": {
        "url": "https://call.example.com",
        "participant_limit": 8,
        "brand": "Element Call"
    }
}

caddy/Caddyfile

For single-machine production deployments (used by quickstart.sh), Caddy runs in the same Docker network as all other services and reverse-proxies to container names.

Key routing rules on the Matrix domain:

  • /.well-known/matrix/client → inline JSON response (includes MAS issuer URL)
  • /.well-known/matrix/server → inline JSON response
  • /_matrix/client/v3/login* etc. → MAS (compat endpoints)
  • /_matrix/* → Synapse
  • /config.json (on Element domain) → inline JSON response

The /.well-known/matrix/client response must include:

{
  "m.homeserver": {"base_url": "https://matrix.example.com"},
  "m.authentication": {"issuer": "https://auth.example.com/"}
}

With Element Call, also add:

{
  "org.matrix.msc4143.rtc_foci": [
    {"type": "livekit", "livekit_service_url": "https://rtc.example.com/livekit/jwt"}
  ]
}

Important: respond body must be on a single line. Caddy does not support multi-line inline response bodies.

For local testing (used by deploy.sh local mode), the Caddyfile uses local_certs globally and tls internal per block instead of Let's Encrypt.

livekit/livekit.yaml

Only needed when Element Call is enabled:

port: 7880

rtc:
  tcp_port: 7881
  port_range_start: 50100
  port_range_end: 50200
  use_external_ip: true

keys:
  livekit-key: YOUR_LIVEKIT_SECRET

The key name livekit-key must match the LIVEKIT_KEY value in docker-compose.yml (hardcoded as livekit-key).


Custom Docker Registry

deploy.sh prompts for two optional image settings:

Custom registry prefix — prepends a registry URL to every image. Useful for air-gapped environments or internal mirrors:

Custom Docker registry prefix: myregistry.example.com

Results in .env entries like:

SYNAPSE_IMAGE=myregistry.example.com/matrixdotorg/synapse:latest
REDIS_IMAGE=myregistry.example.com/redis:7-alpine

Hardened images — uses dhi.io hardened variants for Redis, PostgreSQL, and Caddy. Takes priority over the custom registry prefix for those three images:

REDIS_IMAGE=dhi.io/redis:7
POSTGRES_IMAGE=dhi.io/postgres:16
CADDY_IMAGE=dhi.io/caddy:2

Pull-through cache registries (Harbor, Artifactory, Nexus)

Many enterprise registries act as pull-through caches that mirror images under the full original registry path. For example, Harbor's proxy cache serves Docker Hub images as:

myregistry.example.com/docker.io/library/redis:7-alpine
myregistry.example.com/docker.io/matrixdotorg/synapse:latest
myregistry.example.com/ghcr.io/element-hq/matrix-authentication-service:latest

The custom registry prefix in deploy.sh does not add the docker.io/ or other intermediate path components — it produces myregistry.example.com/redis:7-alpine. If your mirror requires the full path structure, set the image variables manually in .env after running deploy.sh, or configure your registry to serve images without the intermediate path (most registries support this as an alias/rewrite).


Docker Compose Profiles

The docker-compose.yml uses Docker Compose profiles to make services optional:

Profile Services added
single-machine caddy
element-call livekit, lk-jwt-service, element-call
authelia authelia, redis

quickstart.sh always uses --profile single-machine. deploy.sh selects profiles based on user choices.


Port Reference

Container port Host port Service
8008 8008 Synapse (Matrix API + federation)
8448 8448 Synapse (federation, alternate)
8080 8080 MAS
8081 8081 MAS (internal health)
80 8090 Element Web
8080 8091 Element Admin
8080 8083 Element Call
8080 8082 lk-jwt-service
7880 7880 LiveKit
7881/tcp 7881 LiveKit WebRTC signaling
50100-50200/udp 50100-50200 LiveKit media
80, 443 80, 443 Caddy
2019 2019 Caddy admin API
5432 PostgreSQL (internal only)

In single-machine production, Caddy is the only externally visible entry point. The other ports are published for multi-machine setups where Caddy runs on a separate server.


Appservices (Bridges and Double Puppet)

Double Puppet

A synthetic appservice that allows bridges to act as your Matrix user rather than a bot. Lives at appservices/doublepuppet.yaml:

id: doublepuppet
url: null           # null (not empty string) prevents Synapse retry loops
as_token: "YOUR_AS_TOKEN"
hs_token: "YOUR_HS_TOKEN"
sender_localpart: doublepuppet
rate_limited: false

namespaces:
  users:
    - regex: "@.*:yourdomain.com"
      exclusive: false

url: null is critical. An empty string causes Synapse to repeatedly attempt HTTP transactions to the appservice.

Bridge Registration Files

Each bridge generates its own registration.yaml on first successful startup. The file must be:

  • Readable by Synapse (chmod 644)
  • Mounted into Synapse via ./bridges:/bridges:ro
  • Listed in homeserver.yaml under app_service_config_files

Bridges must be configured with hostname: 0.0.0.0 (not 127.0.0.1) so Synapse can reach them across Docker containers.


Element Admin

Element Admin requires three environment variables set on the container:

environment:
  SERVER_NAME: "matrix.example.com"
  OIDC_CLIENT_ID: "01ADMN00000000000000000000"
  OIDC_ISSUER: "https://auth.example.com/"

It listens on port 8080 internally (recent versions changed from port 80). The corresponding host port is 8091.


Manual Secret Generation

If you need to regenerate secrets manually:

# Generic secret (base64, 32 chars)
openssl rand -base64 32 | tr -d "=+/" | cut -c1-32

# Hex secret (for MAS encryption key — must be hex)
openssl rand -hex 32

# MAS signing key (RSA, PKCS8)
openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt > mas-signing.key

Authelia Integration (Optional)

When Authelia is used as an upstream OIDC provider, MAS is configured with:

upstream_oauth2:
  providers:
    - id: '01HQW90Z35CMXFJWQPHC3BGZGQ'
      issuer: 'https://authelia.example.com'
      discovery_url: 'http://authelia:9091/.well-known/openid-configuration'
      client_id: 'mas-client'
      client_secret: 'YOUR_CLIENT_SECRET'
      scope: 'openid profile email offline_access'
      token_endpoint_auth_method: 'client_secret_basic'
      fetch_userinfo: true
      claims_imports:
        localpart:
          action: force
          template: '{{ user.preferred_username }}'
        displayname:
          action: suggest
          template: '{{ user.preferred_username }}'
        email:
          action: force
          template: '{{ user.email }}'
          set_email_verification: always

passwords:
  enabled: false

Key requirements:

  • fetch_userinfo: true is mandatory — Authelia serves claims via userinfo, not the ID token
  • Use preferred_username not name — Authelia does not provide a name claim
  • discovery_url uses the internal container name to avoid SSL certificate trust issues

The Authelia client configuration requires all MAS redirect URI patterns to be registered, including the upstream callback URL with the provider ID.