Self-hosted Discord alternative, Synapse and Element in Docker
12 Jan 2022Discord 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"
}
},
...
- base_url - Due to the name of the previous config I assume this is the url to where the web UI is for example https://element.your-domain.com
- server_name - This is the url to the Matrix (synapse) server, https://matrix.your-domain.com
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