Synapse v1.60.0 Setup:
with Docker Compose v2 and Traefik Proxy v2
Published:
2021-11-14
Modified:
2022-06-24
Category:
sysadmin
Length:
≥10 Minutes
Tags:
Synapse, Matrix, Docker, Docker Compose, Traefik, Traefik Proxy, DNS, Let's Encrypt, Postgres

In this entry, I'll outline the steps I took setting up Synapse v1.60.0, including the configuration I used. For my install, I used simple Username/Password authentication and a 3rd party SMTP provider. I did not up a TURN server, so of course information on that will not be included.

As Docker Compose has feature parity with the Docker CLI commands, Docker Compose is not strictly needed. However, conversion from Docker Compose YAML to Docker CLI is an exercise left to the reader.

Prerequisites

If, for some reason, you are using this as a guide for doing this yourself, you will need the following:

Knowledge

  • Basic understanding of Linux and Ubuntu Server
    (e.g. if you can work your way through this tutorial, you're good)
  • Basic understanding of Docker and Docker Compose
    (e.g. if you can work your way through this tutorial, you're good)
  • Basic understanding of Traefik Proxy and it's Docker Provider
    (e.g. if you can work your way through the Quick Start, you're good)

Resources

  • Publicly Accessible Server
    • Ubuntu 20.04 or newer
    • 4 GB of RAM minimum
    • 8 GB of Storage minimum
    • SSH or Direct Terminal Access
    • Super User Privileges
    • Ports 443 and 8448 free
  • Registered Domain Name
  • Applications and Services

Instructions

Preparing Your Domain

As one of the first steps of setting up Synapse involves informing it of the domain being used, it makes sense to talk about it first.

With Matrix, the domain (aka “server name”), is used as an identifier for the Synapse server (aka “homeserver”) and local users for all interactions with other homeservers and the users on them. Currently, changing the domain name of a homeserver is tantamount to creating a completely new homeserver; there is a proposal for Portable Identities which may help in the future, but for now it's best to choose your server name wisely.

The way the server name is utilized for users is similar to the purpose of the discriminator you would find under your username on Discord, namely to avoid collisions between names (granted, Discord itself just uses each user's snowflake to identify them in actuality, but that's a different topic). If every username had to be unique, we'd have to content with xXx_Bobbert_xXx, Shelia1960, and JeffTheOverlyLongDescriptiveName.

Instead Matrix takes advantage of the fact that each domain can only be registered by a single entity at a time. This also has the advantage of adding a quick way to see what organization a user is associated with, e.g. @jake:state.farm, @bear:big.blue.house, or @geralt:riv.ia.

With all that out of the way, let's continue.

Matrix utilizes TLS/HTTPS for it's client<->server and server<->server communications, the former using the regular port of 443 and the latter Synapse, by default, listens on port 8008 for both client and server HTTP communications. This (intentional) discrepacy will be solved later by routing the communication through a reverse proxy (in this case, Traefik).

If reserving port 443 (or 8448) on your choosen domain for Synapse is an issue, e.g. you also serve a website, there are alternative methods avaliable to you that still let you use it as your server name.

One method is to use your reverse proxy to set certain routes to be handled by Synapse. Allowing everything but /_matrix and /_synapse/client to be fullfiled by other services. This is the easiest option, especially if your services are all handled by the same reverse proxy.

On the other hand, if your situation doesn't allow that. You can also delgate a connection to another domain or port by using a .well-known configuration file on your public server name pointed towards your URI of choice, or create a SRV DNS record allowing server discovery.

Once you've made sure to your domain is correctly pointed to the right place and any required delgation is done, it's on to the next step.

Generating Synapse Configuration

In a working directory of your choice, create the file docker-compose.yml and transcribe the following YAML:

docker-compose.yml
		version: "3.7"

services:
  synapse:
    image: matrixdotorg/synapse:v1.60.0
    container_name: ${SYNAPSE_CONTAINER_NAME}
    environment:
      - SYNAPSE_SERVER_NAME
      - SYNAPSE_REPORT_STATS
      - UID
      - GID
      - TZ
    volumes:
      - "/synapse:/data:rw"
    restart: "unless-stopped"
    ulimits:
      nofile:
        soft: 131072
        hard: 131072
	

Due to the design of Synpase, the default nofile ulimits of 1024 cause noticeable issues. Increasing it to a high enough value (2^17 in this case) will resolve those issues.

Then, to set the environment variables, create the file .env in the same directory and populate it with the following information:

  • SYNAPSE_CONTAINER_NAME: The hostname of the container
  • SYNAPSE_SERVER_NAME: The domain/server name for your homeserver
  • SYNAPSE_REPORT_STATS: Report anonymous stats to the Synpase developers (yes/no)
  • UID/GID: The User/Group ID of the owner of the data directory
  • TZ Which Time Zone to use for timestamps and other time-related functions

An example of a completed .env is as follows:

.env
		SYNAPSE_CONTAINER_NAME=exampleorg-synapse
SYNAPSE_SERVER_NAME=example.org
SYNAPSE_REPORT_STATS=yes
UID=1000
GID=1000
TZ=America/Vancouver
	

Now run 'docker compose run synapse generate' and you should get an output like so:

command output
		julian@server:/docker/synapse$ docker compose run synapse generate
Setting ownership on /data to 1000:1000
Creating log config /data/example.org.log.config
Generating config file /data/homeserver.yaml
Generating signing key file /data/example.org.signing.key
A config file has been generated in '/data/homeserver.yaml' for server name 'example.org'. Please review this file and customise it to your needs.
	

Once it's complete, /synapse should have appeared in the working directory and within it the file needed for the next section.

Editing homeserver.yml

Opening ./synapse/homeserver.yml, you will see that the generated configuration is several thousand lines. Now, before the panic surrounding the idea of having to set thousands of variables kicks in, you should be able to see that the majority of the file is actually comments, meant to explain each section in detail, whew.

While I highly recommend reading through the configuration so you can learn how to fine tune the install to your needs, you only need to focus on a few sections to get a basic homeserver up and running.

Database Configuration

By default, Synapse uses an embedded SQLite driver for it's database, this is fine for development and small, unfederated use cases but quickly becomes constranting when you want to join the main fediverse. This is why we'll need to configure a connection to a Postgres database (that we'll focus on setting up later) here.

In homeserver.yaml find the section labelled with the comment ## Database ## within it, there should be already be configuration that looks like the following:

homeserver.yaml
		database:
  name: sqlite3
  args:
    database: /data/homeserver.db
	

Remove it and replace it with the following:

homeserver.yaml
		database:
  name: psycopg2
  args:
    user: synapse
    password: SuperSecurePassword
    host: exampleorg-postgres
    cp_min: 5
    cp_max: 10
	

Make sure to generate a properly secure password to replace SuperSecurePassword and rename the hostname in line with how you named the Synapse container.

Make sure to keep track of these values as they'll be required later when setting up the Postgres container.

Registration Configuration

When it comes to Registration/Authentication, Synapse really gives you >all the options. From simple usernames and passwords to SSO or oAuth, trying to cover all the options in here would take more space than the rest of this documention combined. Good thing the Synapse Developers already wrote all about that stuff in the official documentation.

All I can really say in this section is that if you want the absolute bare bones way to let people register, find the enable_registration and enable_registration_without_verification key and set them to true, but don't do that for real.

Instead you should look into how you want your organzation's users to sign up (or use alternative authentication) and figure out what validation methods you wish to use (e.g. email, captcha, ). Again, this is all too much to cover here, but that doesn't mean you shouldn't cover your bases.

Postgres Service Set-up

Now that we have Synapse configured, we next need the database. In the project's docker-compose.yml add the following section:

docker-compose.yaml
		  postgres:
    image: postgres:14
    container_name: ${POSTGRES_CONTAINER_NAME}
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_INITDB_ARGS
    volumes:
    - "/postgres:/var/lib/postgres/data:rw"
    restart: unless-stopped
	

as well, in the synapse service configuration, add the following property:

docker-compose.yaml
		    depends_on: 
      - postgres
	

The full docker-compose.yml should look like the following:

docker-compose.yaml
		version: "3.7"

services:
  synapse:
    image: matrixdotorg/synapse:v1.60.0
    container_name: ${SYNAPSE_CONTAINER_NAME}
    environment:
      - SYNAPSE_SERVER_NAME
      - SYNAPSE_REPORT_STATS
      - UID
      - GID
      - TZ
    volumes:
      - "/synapse:/data:rw"
    restart: "unless-stopped"
    ulimits:
      nofile:
        soft: 131072
        hard: 131072
    depends_on:
      - postgres
  postgres:
    image: postgres:14
    container_name: ${POSTGRES_CONTAINER_NAME}
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_INITDB_ARGS
    volumes:
      - "/postgres:/var/lib/postgres/data:rw"
    restart: unless-stopped
	

Then in the same .env as earlier add the following properties:

.env
		POSTGRES_CONTAINER_NAME=exampleorg-postgres
POSTGRES_USER=synapse
POSTGRES_PASSWORD=SuperSecurePassword
POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=C"
	

The first three properties should match the host, user, and >password set earlier in the Synapse configuration. The last one configures the database to the settings expected by Synapse.

The full .env should look like the following:

.env
		SYNAPSE_CONTAINER_NAME=exampleorg-synapse
SYNAPSE_SERVER_NAME=example.org
SYNAPSE_REPORT_STATS=yes
UID=1000
GID=1000
TZ=America/Vancouver
POSTGRES_CONTAINER_NAME=exampleorg-postgres
POSTGRES_USER=synapse
POSTGRES_PASSWORD=SuperSecurePassword
POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=C"
	

That should be all the configuration needed for the database.

Creating Your First User

To create our user, first we need to have Synapse running, so a quick docker compose up -d then wait for the it to pull the images we need and start them.

Once started, running docker compose exec synapse register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008 will get you an interactive prompt to create a user.

It will ask for a new user localpart (username), a password, and if you should make the user an admin (in this case yes).

Once that's complete, you'll have your adminstrative user account, make sure you don't forget the username or password!

Configuring Traefik

Now for the part where we expose our homeserver to the world.

Make sure before following the rest that ports 443 and 8448 are being allowed through your firewall and accessible to Traefik.

Entrypoints

In the static configuration of Traefik, we'll need two EntryPoints for Synapse, one for each port, both configured as normal HTTP + TLS.

traefik.toml
		[entryPoints.matrixClient]
	address = ":443"
	[entryPoints.matrixClient.http.tls]
		certResolver = "lets-encrypt"
[entryPoints.matrixFederate]
	address = ":8448"
	[entryPoints.matrixFederate.http.tls]
		certResolver = "lets-encrypt"
	

Note, you don't have to name your entryPoints "matrixClient" and "matrixFederated" if that conflicts with other things. You just need to remember what they are.

Make sure to restart your Traefik instance for it to discover the new configuration.

Routing Synapse

Back in the docker-compose.yml, we now need to add some labels to our containers.

For the Synapse container we need to tell Traefik how to route it from the EntryPoints we defined above to the port that Synapse is listening to. In the Synapse service configuration add the following key and list:

docker-compose.yaml
		    labels:
      - "traefik.http.services.synapse.loadbalancer.server.port=8008"
      - "traefik.http.routers.synapse.rule=Host(`example.org`)"
      - "traefik.http.routers.synapse.entryPoints=matrixClient,matrixFederate"
      - "traefik.http.routers.synapse.service=synapse"
	

And in the Postgres service configuration add the following:

docker-compose.yaml
		    labels:
      - "traefik.enable=false"
	

This is to ensure that Traefik does not try to autodiscover any ports on container postgres and publish them.

Lastly, at the bottom of docker-compose.yml configure the containers to be on the same network as Traefik

docker-compose.yaml
		networks:
  default:
    external: true
    name: traefikNetworkHere
	

All that being done, docker-compose.yml should look like the following:

docker-compose.yaml
		version: "3.7"

services:
  synapse:
    image: matrixdotorg/synapse:v1.60.0
    container_name: ${SYNAPSE_CONTAINER_NAME}
    environment:
      - SYNAPSE_SERVER_NAME
      - SYNAPSE_REPORT_STATS
      - UID
      - GID
      - TZ
    volumes:
      - "/synapse:/data:rw"
    restart: "unless-stopped"
    ulimits:
    nofile:
	    soft: 131072
	    hard: 131072
    depends_on:
      - postgres
    labels:
      - "traefik.http.services.synapse.loadbalancer.server.port=8008"
      - "traefik.http.routers.synapse.rule=Host(`example.org`)"
      - "traefik.http.routers.synapse.entryPoints=matrixClient,matrixFederate"
      - "traefik.http.routers.synapse.service=synapse"
  postgres:
    image: postgres:14
    container_name: ${POSTGRES_CONTAINER_NAME}
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_INITDB_ARGS
    volumes:
      - "/postgres:/var/lib/postgres/data:rw"
    restart: unless-stopped
    labels:
      - "traefik.enabled=false"

networks:
  default:
    external: true
    name: traefikNetworkHere
	

And that should be it, restart your containers and Traefik should automatically discover them and get a certificate for your domain.

All that's left to do is use a Matrix Client to connect to your homeserver and/or test your server's ability to federate with the Federation Tester.

Conclusion

Congrats! You should now have a basic Synapse install.

I highly recommend you read up on the documentation and configure it to suit your organization's needs. Inital set-up like this is just the tip of the iceberg

Further Reading