FICUSONLINE F9E
Chat / Video Conference System: Tuwunel ( Matrix Server ) on Docker
Matrix is a relatively new decentralized open-source communication protocol designed for real-time online communication. It supports one-to-one messaging and voice calls, as well as group chats and video conferencing. Matrix-compliant servers are built on a decentralized architecture, enabling users on different servers to seamlessly communicate with each other through a common protocol, similar to the way email works.
Takanobu FuseAdministrator

5 months ago

Cloud / Server

Building a Matrix Server

This document explains how to deploy and operate a chat and video calling system using the Matrix protocol with Docker containers.

This is a reorganized version of the forum article Forun article: Tuwunel ( [Matrix] Server ) for Docker

  • What is Matrix?
  • Benefits of Implementation
  • Features
  • Matrix Server
  • Matrix Client
  • System Requirements
  • System Architecture
  • Configuration Files for Each Container
  • Docker Compose File
  • Startup and Operation

What is Matrix?

Matrix is a relatively new decentralized open-source communication protocol designed for real-time online communication. It supports one-to-one messaging and voice calls, as well as group chats and video conferencing.

Matrix-compliant servers are built on a decentralized architecture, enabling users on different servers to seamlessly communicate with each other through a common protocol, similar to the way email works.

Chats and calls are protected through end-to-end encryption provided by the client application, ensuring a high level of security. In rooms where end-to-end encryption is enabled, messages are stored on the server in encrypted form and cannot be decrypted without the encryption keys stored on the client device or the recovery key backup information.

Benefits of Implementation

Implementing Matrix enables organizations to unify their communication infrastructure for both internal and external collaboration, greatly enhancing the speed, accuracy, and reliability of information sharing.

By integrating chat, voice, and video communication into a single platform, organizations can consolidate communication channels that were previously spread across email, phone systems, social media, and multiple messaging tools. This leads to lower operational costs and improved workflow efficiency.

Because Matrix is built on a decentralized protocol, it supports independent deployment on private servers, minimizing dependence on third-party services. As a result, confidential business information and project data can be securely managed within the organization’s own infrastructure.

Its end-to-end encryption capabilities help mitigate the risk of unauthorized access and data leakage, making it well suited for environments with strict security and compliance requirements. This is especially valuable for engineering and development teams, healthcare and educational institutions, and customer support operations.

In addition, its flexible space and room structure allows teams to organize communication by project, department, or function, making it highly effective for cross-functional collaboration and remote work.

Bridge integration with existing social media and external chat services also enables phased migration, allowing organizations to leverage their current user base and communication assets.

Features

When using a self-hosted server, user registration can be restricted through invitation-only access or token-based registration controls, depending on the homeserver implementation in use.

In addition, administrators can be granted access privileges to dedicated administrative rooms and management APIs, enabling them to perform various operational tasks such as user management and room administration.

Users can create Spaces as needed and organize multiple public or private rooms within them. In private rooms, users can invite others to start chats or voice and video calls.

Spaces and rooms can be created without limitation, and their names can be freely defined. For example, a Space can be configured as “Design Project Team” with rooms such as “Mechanical,” “Electrical,” and “Software,” creating a virtual workspace that enables real-time communication among team members.

Matrix Sever

The following are servers that comply with the Matrix protocol. In this case, we will adopt Tuwunel, an open-source server implementation written in Rust.

continuwuity

  • Stable
  • Apache-2.0 Rust
  • Continuwuity, a community driven 2nd degree fork of Conduit focusing on user experience and new features.
  • Repository Matrix Room

Synapse Pro

  • Stable
  • Element Commercial License Python and Rust
  • Synapse for Enterprise, with a focus on performance, scalability and compliance.
  • Homepage Matrix Room

Tuwunel

  • Stable
  • Apache-2.0 Rust
  • Enterprise successor to conduwuit, the high-performance and feature-rich fork of Conduit.
  • Repository Matrix Room

Synapse

  • Stable
  • AGPL-3.0-or-later OR Element Commercial License Python
  • Synapse is a Matrix homeserver written in Python/Twisted.
  • Repository Matrix Room

Conduit

  • Beta
  • Apache-2.0 Rust
  • Conduit is a simple, fast and reliable chat server written in Rust
  • Repository Matrix Room

Dendrite

  • Beta
  • AGPL-3.0-or-later OR Element Commercial License Go
  • Dendrite is a second-generation Matrix homeserver written in Go!
  • Repository Matrix Room

Telodendria

  • Alpha
  • MIT C
  • Telodendria is an open source Matrix homeserver implementation written from scratch in ANSI C and designed to be lightweight and simple, yet functional.
  • Repository Matrix Room

Matrix Client

Some of the major client applications compatible with the Matrix protocol are listed below, although many other options are also available. For this implementation, we will use Element X for Android and Element Web.

While Element Web is publicly available through the official website, it can also be deployed and hosted on your own infrastructure.

Nheko

Cinny

Element Web / Desktop

Element X

FluffyChat

System Requirements

Host PC

VM Created with QEMU/KVM:Debian13(2 core CPU, Memory 2GB, Storage 60GB)

Domains

System Architecture

Tuwunel and the Element Web frontend are deployed as Docker containers.

The overall system architecture based on Docker containers is illustrated below. TLS certificate management for Coturn is handled by a dedicated Certbot container running continuously in the background.

Matrix Docker System

Open Ports

  • 7880:7881/tcp ALLOW Anywhere
  • 80/tcp (v6) ALLOW Anywhere (v6)
  • 443/tcp (v6) ALLOW Anywhere (v6)
  • 3478 (v6) ALLOW Anywhere (v6)
  • 5349/tcp (v6) ALLOW Anywhere (v6)
  • 7880:7881/tcp (v6) ALLOW Anywhere (v6)
  • 50201:50400/udp (v6) ALLOW Anywhere (v6)

Configuration Files for Each Container

The configuration files for each container are listed below.

Tuwunel serves as the Matrix homeserver container. Caddy functions as both the TLS termination and reverse proxy container. LiveKit is deployed as the WebRTC SFU (Selective Forwarding Unit) container used for real-time communication services. Coturn acts as the TURN server container utilized when NAT traversal is required for LiveKit connectivity.

For detailed configuration rules and file syntax, please refer to the official documentation provided by each service.

Caddyfile

matrix.example.com {
    handle /.well-known/matrix/server {
        header Content-Type "application/json"
        respond `{"m.server":"matrix.example.com:443"}`
    }
    handle /.well-known/matrix/client {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"},"m.identity_server":{"base_url":"https://vector.im"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://matrix-rtc.example.com"}]}`
    }
    reverse_proxy homeserver:6167
}

matrix-rtc.example.com {
    @jwt_service {
        path /sfu/get* /healthz* /get_token*
    }
    handle @jwt_service {
        reverse_proxy matrix-rtc-jwt:8081
    }
    handle {
        reverse_proxy host.docker.internal:7880 {
            header_up Connection "upgrade"
            header_up Upgrade {http.request.header.Upgrade}
        }
    }
}

chat.example.com { 
    handle .well-known/matrix/client { 
      header Content-Type application/json 
      header Access-Control-Allow-Origin * 
      respond `{ "m.homeserver": { "base_url": "https://matrix.example.com" }, "m.identity_server": { "base_url": "https://vector.im" } }` 200 } 
    reverse_proxy element-web:80 
    }
  • Check Caddyfile Syntax $ docker compose exec caddy caddy validate --config /config/caddy/Caddyfile
  • Check Certificate $ ls -la ./data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/

tuwunel.toml

[global]
server_name = "matrix.example.com"
database_path = "/var/lib/tuwunel"
address = ["0.0.0.0", "::"]
port = 6167
max_request_size = 20000000
allow_registration = true
registration_token = "xxxxxxxxxxxxxxxxxxxxx"
allow_federation = true
trusted_servers = ["matrix.org"]
turn_uris = ["turns:turn.example.com:5349?transport=tcp", "turns:turn.example.com:5349?transport=udp"]
turn_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

[global.well_known]
client = "https://matrix.example.com"
server = "matrix.example.com:443"
livekit_url = "https://matrix-rtc.example.com"

Note: The registration_token is required for user registration on the homeserver domain matrix.example.com. If https://matrix-rtc.example.com is configured in Caddy under .well-known/matrix/client, the [global.well_known] section is unnecessary and can be completely commented out.

livekit.yaml

port: 7880
bind_addresses:
  - "::"

rtc:
  tcp_port: 7881
  port_range_start: 50301
  port_range_end: 50400
  use_external_ip: true
  enable_loopback_candidate: false
  turn_servers:
  - host: turn.example.com
    port: 5349
    protocol: tls
    secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

keys:
  xxxxxxxxxxxxxxxxx: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Note: In the keys: section, enter the MRTCKET and MRTCSECRET values defined in the .env file.


turnserver.conf

listening-port=3478
tls-listening-port=5349
listening-ip=::
realm=turn.example.com
use-auth-secret
static-auth-secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
log-file=stdout
no-tcp-relay
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
denied-peer-ip=::1
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
allowed-peer-ip=2404:7a87:9140:1000:5054:ff:fe8e:4360
user-quota=12 # 4 streams per video call, so 12 streams = 3 simultaneous relayed calls per user.
total-quota=1200
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
external-ip=EXTERNAL_IPV6
min-port=50201
max-port=50300

element-config.json

{
    "default_server_name": "example.com",
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://matrix.example.com"
        },
        "m.identity_server": {
            "base_url": "https://vector.im"
        }
    },
    "brand": "Element",
    "integrations_ui_url": "https://scalar.vector.im/",
    "integrations_rest_url": "https://scalar.vector.im/api",
    "integrations_widgets_urls": [
        "https://scalar.vector.im/_matrix/integrations/v1",
        "https://scalar.vector.im/api",
        "https://scalar-staging.vector.im/_matrix/integrations/v1",
        "https://scalar-staging.vector.im/api"
    ],
    "show_labs_settings": true,
    "room_directory": {
        "servers": ["matrix.example.com", "matrix.org"]
    },
    "enable_presence_by_hs_url": {
        "https://matrix.example.com": true
    },
    "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
    "setting_defaults": {
        "RustCrypto.staged_rollout_percent": 60
    },
    "features": {
        "feature_video_rooms": true,
        "feature_group_calls": true,
        "feature_element_call_video_rooms": true
    },
    "element_call": {
        "url": "https://call.element.io",
        "use_exclusively": false
    }
}

Docker Compose File


docker-compose.yaml

services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    container_name: caddy
    ports:
      - 80:80
      - 443:443
    environment:
      - CADDY_INGRESS_NETWORKS=caddy
      - CADDY_DOCKER_CADDYFILE_PATH=/config/caddy/Caddyfile
    networks:
      - caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/data
      - ./Caddyfile:/config/caddy/Caddyfile
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"
      
  homeserver:     
    image: jevolk/tuwunel:latest
    container_name: tuwunel
    restart: unless-stopped
    volumes:
      - ./db:/var/lib/tuwunel
      - ./tuwunel.toml:/etc/tuwunel.toml
    environment:
      TUWUNEL_CONFIG: '/etc/tuwunel.toml'
    networks:
      - caddy
  
  matrix-rtc-jwt:
    image: ghcr.io/element-hq/lk-jwt-service:latest
    container_name: jwt
    environment:
      - LIVEKIT_JWT_BIND=:8081
      - LIVEKIT_URL=wss://matrix-rtc.example.com
      - LIVEKIT_KEY=${MRTCKEY}
      - LIVEKIT_SECRET=${MRTCSECRET}
      - LIVEKIT_FULL_ACCESS_HOMESERVERS=matrix.example.com
    restart: unless-stopped
    networks:
      - caddy

  matrix-rtc-livekit:
    image: livekit/livekit-server:latest
    container_name: livekit
    command: --config /etc/livekit.yaml
    restart: unless-stopped
    volumes:
      - ./livekit.yaml:/etc/livekit.yaml:ro
    network_mode: "host"
    
  # Certbot just only for Coturn TLS  
  certbot:
    image: certbot/dns-cloudflare
    container_name: certbot
    restart: unless-stopped
    volumes:
      - ./certbot/certs:/etc/letsencrypt
      - ./certbot/cloudflare.ini:/etc/letsencrypt/cloudflare.ini:ro
      - /var/run/docker.sock:/var/run/docker.sock
      - ./certbot/deploy-hook.sh:/etc/letsencrypt/renewal-hooks/deploy/restart-coturn.sh:ro
    entrypoint: /bin/sh -c
    command: >
      "certbot certonly
      --dns-cloudflare
      --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
      --dns-cloudflare-propagation-seconds 30
      -d turn.example.com
      --email [email protected]
      --agree-tos
      --non-interactive
      && trap exit TERM;
      while :; do
        sleep 12h &
        wait $${!};
        certbot renew --quiet;
      done"

  coturn:
    image: docker.io/coturn/coturn
    container_name: coturn    
    restart: unless-stopped
    network_mode: host
    user: root
    volumes:
      - ./turnserver.conf:/etc/coturn/turnserver.conf
      - ./certbot/certs:/etc/letsencrypt:ro 

  element-web: # https://hub.docker.com/r/vectorim/element-web 
    image: vectorim/element-web:latest 
    container_name: element-web 
    volumes: - ./element-config.json:/app/config.json:ro 
    networks: - caddy

networks:
  caddy:
    external: true

Note: - LIVEKIT_KEY=${MRTCKEY} —> A set of characters created with “$ pwgen -s 20 1” , then write down it .env file. - LIVEKIT_SECRET=${MRTCSECRET} A set of characters created with ”$ pwgen -s 64 1” , then write down it .env file.

Startup and Operation

Start System, Register Users

Execute the following Docker Compose command:

$ docker compose up -d

After startup, use a Matrix client such as Element X or the web application to register a user account.

User registration requires:

  • Matrix Sever Domain: matrix.example.com
  • Registration Token configured in tuwunel.toml

The first registered user is automatically granted administrator privileges and is allowed to join the management room. The management room is created by default when Tuwunel is started for the first time.

Note: With the following default configuration, the first registered user will automatically be added to the management room.

tuwunel.toml

grant_admin_to_first_user = true
create_admin_room = true

Other users can either register by using a token issued by an administrator, or log in using a username and password created by the administrator.

Operations by the Administrator

The administrator can perform various operations, such as user registration and creation, within the management room.

name: matrix.example.com Admin Room

To execute administrative actions in this room, enter the following command in the message input field:

”!admin + command"

Example) Help: Command List

!admin help

> Usage: !admin <COMMAND>

Commands:
  appservices  - Commands for managing appservices
  users        - Commands for managing local users
  rooms        - Commands for managing rooms
  federation   - Commands for managing federation
  server       - Commands for managing the server
  media        - Commands for managing media
  debug        - Commands for debugging things
  query        - Low-level queries for database getters and iterators
  token        - Commands for managing registration tokens
  help         Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

Client: Element-Web

Element-Web sign-in

Element-Web create room

Element-Web invite

Element-Web chat

Element-Web video

Element-Web group video

Client: Element-X

Element-X approve

Element-X chat room list

Element-X chat

Element-X participate chat

Element-X group video