Files
homelab/continuwuity/docker-compose.yml

181 lines
7.9 KiB
YAML
Raw Normal View History

# ============================================================
# Matrix Self-Hosted Stack
# Services: Continuwuity, Coturn, LiveKit, lk-jwt-service
#
# Domains (replace throughout):
# matrix.example.com — homeserver
# livekit.example.com — LiveKit + JWT service
#
# Before starting:
# 1. Fill in all YOUR_* placeholders
# 2. Set your email for Let's Encrypt
# 3. Run: docker network create proxy
# 4. Then: docker compose up -d
# 5. Grab admin password: docker compose logs homeserver | grep "Created user"
# 6. Remove the --execute flag from homeserver command after first boot
# ============================================================
services:
# ----------------------------------------------------------
# Traefik — Reverse Proxy & TLS termination
# Handles HTTPS for matrix.example.com and livekit.example.com
# Does NOT handle coturn (raw UDP/TCP, not HTTP)
# ----------------------------------------------------------
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- acme:/etc/traefik/acme
environment:
TRAEFIK_LOG_LEVEL: INFO
TRAEFIK_ENTRYPOINTS_WEB_ADDRESS: ":80"
TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_TO: websecure
TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS: ":443"
TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_TLS_CERTRESOLVER: letsencrypt
# Allow encoded characters needed by Matrix
TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDSLASH: "true"
TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDHASH: "true"
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT: "true"
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL: "you@example.com" # EDIT THIS
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_KEYTYPE: EC384
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE: "true"
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE_ENTRYPOINT: web
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_STORAGE: /etc/traefik/acme/acme.json
TRAEFIK_PROVIDERS_DOCKER: "true"
TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT: "false"
networks:
- proxy
# ----------------------------------------------------------
# Continuwuity — Matrix Homeserver
# ----------------------------------------------------------
homeserver:
image: forgejo.ellis.link/continuwuation/continuwuity:latest
container_name: continuwuity
restart: unless-stopped
# Remove the --execute flag after first boot once you have your admin password
command: /sbin/conduwuit --execute "users create-user admin"
depends_on:
- traefik
volumes:
- db:/var/lib/continuwuity
- ./continuwuity.toml:/etc/continuwuity.toml:ro
# Use host DNS to avoid Docker's resolver causing federation issues
- /etc/resolv.conf:/etc/resolv.conf:ro
networks:
- proxy
labels:
- "traefik.enable=true"
# Serve matrix.example.com AND well-known paths on example.com for delegation
- "traefik.http.routers.continuwuity.rule=(Host(`matrix.example.com`) || (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))"
- "traefik.http.routers.continuwuity.entrypoints=websecure"
- "traefik.http.routers.continuwuity.tls.certresolver=letsencrypt"
- "traefik.http.services.continuwuity.loadbalancer.server.port=6167"
environment:
CONTINUWUITY_SERVER_NAME: "example.com" # Your Matrix ID domain (@user:example.com)
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 6167
CONTINUWUITY_MAX_REQUEST_SIZE: "20000000" # ~20 MB
CONTINUWUITY_ALLOW_REGISTRATION: "false" # Enable with a token for invite-only
CONTINUWUITY_REGISTRATION_TOKEN: "YOUR_INVITE_TOKEN" # EDIT THIS
CONTINUWUITY_ALLOW_FEDERATION: "true"
CONTINUWUITY_ALLOW_CHECK_FOR_UPDATES: "true"
CONTINUWUITY_TRUSTED_SERVERS: '["matrix.org"]'
CONTINUWUITY_CONFIG: /etc/continuwuity.toml
# Well-known delegation so clients and servers find your homeserver
CONTINUWUITY_WELL_KNOWN: |
{
client=https://matrix.example.com,
server=matrix.example.com:443
}
ulimits:
nofile:
soft: 1048567
hard: 1048567
# ----------------------------------------------------------
# Coturn — TURN/STUN server for legacy 1:1 calls
# Uses host networking — Traefik does NOT proxy this.
# Reachable directly on the host IP via UDP/TCP ports 3478/5349
# and the relay port range 50201-65535.
# ----------------------------------------------------------
coturn:
image: coturn/coturn:latest
container_name: coturn
restart: unless-stopped
network_mode: host
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf:ro
# ----------------------------------------------------------
# LiveKit — Media server for MatrixRTC / Element Call
# Also uses host networking for RTC performance.
# Traefik proxies HTTP/WS on port 7880 via livekit.example.com.
# ----------------------------------------------------------
livekit:
image: livekit/livekit-server:latest
container_name: livekit
restart: unless-stopped
command: --config /etc/livekit.yaml
network_mode: host
volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro
# Note: with network_mode: host, Traefik cannot directly label this container.
# The livekit.example.com routing is handled via the lk-jwt-service container
# labels, with LiveKit itself accessed at 127.0.0.1:7880 from that container's
# perspective. See lk-jwt-service labels below.
# ----------------------------------------------------------
# lk-jwt-service — Issues JWT tokens for LiveKit
# Matrix users authenticate here before joining calls.
# Traefik routes livekit.example.com here, with path-based
# splitting: /sfu/get, /healthz, /get_token go to this service;
# everything else is reverse-proxied onward to LiveKit on :7880.
# ----------------------------------------------------------
lk-jwt-service:
image: ghcr.io/element-hq/lk-jwt-service:latest
container_name: lk-jwt-service
restart: unless-stopped
environment:
- LIVEKIT_JWT_BIND=:8081
- LIVEKIT_URL=wss://livekit.example.com # EDIT: your LiveKit domain
- LIVEKIT_KEY=YOUR_LK_KEY # EDIT: from generate-keys
- LIVEKIT_SECRET=YOUR_LK_SECRET # EDIT: from generate-keys
- LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com # EDIT: your Matrix domain
ports:
- "127.0.0.1:8081:8081" # Only bind to localhost; Traefik reaches it here
networks:
- proxy
labels:
- "traefik.enable=true"
# High-priority rule: JWT paths go to lk-jwt-service
- "traefik.http.routers.livekit-jwt.rule=Host(`livekit.example.com`) && (PathPrefix(`/sfu/get`) || PathPrefix(`/healthz`) || PathPrefix(`/get_token`))"
- "traefik.http.routers.livekit-jwt.entrypoints=websecure"
- "traefik.http.routers.livekit-jwt.tls.certresolver=letsencrypt"
- "traefik.http.routers.livekit-jwt.priority=10"
- "traefik.http.services.livekit-jwt.loadbalancer.server.port=8081"
# Low-priority rule: everything else on livekit.example.com goes to LiveKit itself
- "traefik.http.routers.livekit.rule=Host(`livekit.example.com`)"
- "traefik.http.routers.livekit.entrypoints=websecure"
- "traefik.http.routers.livekit.tls.certresolver=letsencrypt"
- "traefik.http.routers.livekit.priority=1"
- "traefik.http.services.livekit.loadbalancer.server.url=http://127.0.0.1:7880"
# ----------------------------------------------------------
# Volumes & Networks
# ----------------------------------------------------------
volumes:
db:
acme:
networks:
proxy:
external: true