Self-hosted Discord alternative, Synapse and Element in Docker

12 Jan 2022

Discord is very popular chat service and easy to use. But it is not self-hosted, and self-hosted services is way more fun. So lets setup a self-hosted Discord alternative, Using the open-source server based on the Matrix ecosystem for instant messaging, VoIP and video chat. Synapse will be used as our backend with Element as the web base frontend.

The Element web UI is similar to Disocrds UI. In my opinion this is good because most people are used to Discord.

The Matrix based backend is different though and it is an open protocol so there are lots of other Clients if you don’t like the Element web UI.

Just a warning though, from what I have been reading it is not recommended to run the Matrix/synapse backend server on the same domain as the Element frontend server.

Some places says it’s fine to run it on different sub-domains, others says you should have different domains. I’m going with the first one, two different sub-domains to the same domain.

Synapse matrix server

Lets start with the backend server, because there are lots of desktop clients and mobile clients you can use to test the server with.

version: '3'
  synapse:
    image: matrixdotorg/synapse:v1.49.2
    container_name: synapse
    # Since synapse does not retry to connect to the database, restart upon
    # failure
    restart: unless-stopped
    # See the readme for a full documentation of the environment settings
    environment:
       TZ: "Europe/Stockholm"
       SYNAPSE_SERVER_NAME: "matrix.<YOUR-DOMAIN>"
       SYNAPSE_ENABLE_REGISTRATION: "true"
       SYNAPSE_REPORT_STATS: "yes"
    volumes:
      - ./synapse:/data
    # In order to expose Synapse, remove one of the following, you might for
    # instance expose the TLS port directly:
    ports:
      - 8448:8448/tcp
      - 8008:8008
      - 8009:8009

This is using the official synapse server image with some variables

  • TZ - Timezone
  • SYNAPSE_SERVER_NAME - This is where you put your domain name for the server, recommended to use at least a separate sub-domain for the matrix server.
  • SYNAPSE_ENABLE_REGISTRATION - To enable users to register on the server
  • SYNAPSE_REPORT_STATS - Report anonymous user statistics

SYNAPSE_SERVER_NAME and SYNAPSE_REPORT_STATS are mandatory environment variables. SYNAPSE_ENABLE_REGISTRATION can be removed.

for persistent storage I am using a subfolder in the same location where I have my docker-compose.yml.

Create persistent storage folder

Before we are starting our Synapse server we need to create the persistent storage. In the same directory where you have your docker-compose.yml, create the synapse folder.

The synapse docker container will change the ownership of the persistent storage folder to the user and group in the container. By default this is UID=991 and GID=991. This can be changed using the environment variables in  the docker-compose.yml file.

I have my storage on my NAS, so my virtual machine is mounting this storage with NFS4. But for some reason I can’t change ownership on folders in my NFS share, so the start process of the docker container failed. I solved this by changing the permissions on the folder on my server.

Generate Synapse config file

Before we can start our server we need a config file. Synapse can generate a default config file for us.

docker-compose run --rm synapse generate

This will generate a homeserver.yaml in our ./synapse folder. There are plenty of configuration settings to change, but I’m running with the default for now.

Generate admin user for Synapse server

First we need to set a registration_shared_secret variable in our config file.

Start and test our synapse server

docker-compose up -d

This will start our server, now we can browse to http://<server-ip>:8008

If you can see this webpage you are good to go.

SQL Database

By default the Synapse server will be using SQLite3 but it is recommended that you use PostgreSQL or other supported database. My server will be very small scale for now so I will continue with the default SQLite database.

Short untested docker-compose.yml for a PostgreSQL for the synapse server.

postgresql:
        image: postgres:latest
        restart: always
        environment:
            POSTGRES_USER: synapse
            POSTGRES_PASSWORD: somepassword
            POSTGRES_DB: synapse
            POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"
        volumes:
            - ./postgresql:/var/lib/postgresql/

Read more over at Synapse Github

Changes to homeserver.yml

Remove SQLite3 configuration

database:
    name: sqlite3
    args:
        database: /path/to/homeserver.db

./synapse/homeserver.yaml - Remove SQLite3 configuration

Add PostgreSQL configuration

database:
    name: psycopg2
    args:
        user: synapse
        password: somepassword
        host: postgresql
        database: synapse
        cp_min: 5
        cp_max: 10

Element web UI

The web frontend has changed names a few times. Last time I looked into this project it was called Riot.

Add this to your docker-compose.yml

  element-web:
    image: vectorim/element-web:v1.9.8
    container_name: element-web
    restart: unless-stopped
    volumes:
      - ./element-web/config.json:/app/config.json
    depends_on:
      - synapse
    ports:
      - 80:80

Before we start the Element web user interface we need to create the ./element-web/config.json because if it doesn’t exist Docker will create a folder in it’s place and Element docker container will freak out when it is finding a folder instead of a json file.

This command will extract the default config.json file from the element-web docker container

docker run --rm vectorim/element-web:v1.9.8 cat /app/config.json > ./element-web/config.json

Few changes need to be made

{
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://matrix-client.matrix.org",
            "server_name": "matrix.org"
        },
        "m.identity_server": {
            "base_url": "https://vector.im"
        }
    },
...

When you have change those settings you are good to go AFAIK

docker-compose up -d

To start synapse and element-web services.

TURN server

Chat and VoIP was working fine for me, but video calls did not work. After a bit of googling I found that you need a TURN server to be able to use video calls.

I don’t know a lot about TURN servers. But I managed to get Coturn working

  coturn:
    image: coturn/coturn:4.5.2
    container_name: coturn
    restart: unless-stopped
    environment:
      LOG_FILE: stdout
      PORT: 3478
      ALT_PORT: 3479
      TLS_PORT: 5349
      TLS_ALT_PORT: 5350
      JSON_CONFIG: '{"config":["no-auth"]}'
    volumes:
      - ./turnserver.conf:/etc/turnserver.conf:ro
      - ./coturn:/var/lib/coturn
    ports:
## STUN/TURN
      - 3478:3478
      - 3478:3478/udp
      - 3479:3479
      - 3479:3479/udp
## STUN/TURN SSL
#      - 5349:5349
#      - 5349:5349/udp
#      - 5350:5350
#      - 5350:5350/udp
## Relay Ports
#      - 49160-49200:49160-49200
#      - 49160-49200:49160-49200/udp
  • LOG_FILE - This is set to stdout, so the log is visible through docker logs -f coturn
  • PORT / ALT_PORT - This is the ports for non SSL communication, you need to open those in your firewall AFAIK
  • TLS_PORT / TLS_ALT_PORT - Ports to use if you want to use SSL

Persistent storage

Coturn container is running as user ’nobody’ so lets create the persistent data storage for coturn and make sure nobody is the owner

mkdir coturn
chown nobody coturn

Coturn configuration

Found some simple turnserver.conf file settings that is working for me

realm=turn.YOUR-DOMAIN.COM

user=test:test666
lt-cred-mech

use-auth-secret
static-auth-secret=<long random string>

listening-ip=0.0.0.0
listening-port=3478

# Relay port range limit
min-port=49160
max-port=49200

To generate the static-auth-secret I’m using pwgen

pwgen -s 64 1
Oe4BG6oZGGv2jJ9mWDXCsJhVkeC1Azi09TMfaOiAJBtUqUamAvVHHwl1Nj9f5wQp

Then just copy the random long string and paste it in your turnserver.conf file.

Synapse configuration changes for TURN server

To enable the synapse server to connect to the TURN server some changes has to be made in the synapse/homeserver.yaml

This neds to be changed in the homeserver.yaml file. By default they are commented out

turn_uris: [ "turn:turn.YOUR-DOMAIN.COM?transport=udp", "turn:turn.YOUR-DOMAIN.COM?transport=tcp" ]
turn_shared_secret: "Oe4BG6oZGGv2jJ9mWDXCsJhVkeC1Azi09TMfaOiAJBtUqUamAvVHHwl1Nj9f5wQp"
turn_user_lifetime: 86400000
turn_allow_guests: True

Shutdown and restart everything just to make sure the new config is used

docker-compose down
docker-compose up -d

Complete all-in-one Docker-compose

This is the complete docker-compose.yml file for Synapse, Element-web and Coturn

version: '3'

services:
  element-web:
    image: vectorim/element-web:v1.9.8
    container_name: element-web
    restart: unless-stopped
    volumes:
      - ./element-web/config.json:/app/config.json
    depends_on:
      - synapse
    ports:
      - 80:80

  synapse:
    image: matrixdotorg/synapse:v1.49.2
    container_name: synapse
    # Since synapse does not retry to connect to the database, restart upon
    # failure
    restart: unless-stopped
    # See the readme for a full documentation of the environment settings
    environment:
       SYNAPSE_SERVER_NAME: "matrix.YOUR-DOMAIN.COM"
       SYNAPSE_ENABLE_REGISTRATION: "true"
       SYNAPSE_REPORT_STATS: "yes"
    volumes:
      - ./synapse:/data
    depends_on:
       - coturn
    # In order to expose Synapse, remove one of the following, you might for
    # instance expose the TLS port directly:
    ports:
      - 8448:8448/tcp
      - 8008:8008
      - 8009:8009

  coturn:
    image: coturn/coturn:4.5.2
    container_name: coturn
    restart: unless-stopped
    environment:
      LOG_FILE: stdout
      PORT: 3478
      ALT_PORT: 3479
      TLS_PORT: 5349
      TLS_ALT_PORT: 5350
      MIN_PORT: 49160
      MAX_PORT: 49200
      JSON_CONFIG: '{"config":["no-auth"]}'
    volumes:
      - ./turnserver.conf:/etc/turnserver.conf:ro
      - ./coturn:/var/lib/coturn
    ports:
## STUN/TURN
      - 3478:3478
      - 3478:3478/udp
      - 3479:3479
      - 3479:3479/udp
## STUN/TURN SSL
#      - 5349:5349
#      - 5349:5349/udp
#      - 5350:5350
#      - 5350:5350/udp
## Relay Ports
#      - 49160-49200:49160-49200
#      - 49160-49200:49160-49200/udp