This is how I set up my own Matrix server on a Raspberry Pi with Docker. Unfortunately, the Matrix community has stopped releasing ARM images, so the latest version that will work on ARM is v1.26.0. These instructions will work the same for x86_64 systems, except you’ll be able to use the default x86_64 images in the docker-compose file.

This installation comes with Maubot and matrix-registration containers too. If you don’t want to use those features, leave out those sections of the docker-compose config and don’t follow the instructions in the corresponding sections.

First, switch the Raspberry Pi to 64-bit kernel mode for performance:

echo 'arm_64bit=1' | sudo tee -a /boot/config.txt
sudo systemctl reboot

Create a directory for the services we’re going to provide:

mkdir -p ~/services/data/{certbot,maubot,nginx,postgres,registration,synapse,www}
cd ~/services

The rest of the commands below will assume the working directory is ~/services.

federation

The homeserver is going to be hosted on matrix.kylrth.com, but the server name is kylrth.com. (That way usernames can be @kyle:kylrth.com instead of @kyle:matrix.kylrth.com, which would be sort of like having an email address kyle@email.kylrth.com.) For federation to work correctly, other servers need to know that kylrth.com uses matrix.kylrth.com as its server. To do that, create the file kylrth.com/.well_known/matrix/server with the contents { "m.server": "matrix.kylrth.com:443" }. We should also add the following line to /etc/hosts so that any outgoing requests to matrix.kylrth.com are routed right back to the machine:

127.0.0.1 matrix.kylrth.com

Another note: you may want to change the DNS server to something better. I added the following line to /etc/dhcpcd.conf:

static domain_name_servers=1.1.1.1 1.0.0.1 8.8.4.4 8.8.8.8

And then do sudo service dhcpcd restart.

docker-compose

Now install docker and docker-compose following the instructions here. Then create docker-compose.yml:

wget https://kylrth.com/post/matrix-setup/docker-compose.yml \
    -O docker-compose.yml

You’ll note that in several places I set the user to 1000:1000. I did this so that data files are owned by my user, plus it’s generally safer not to run containers as root. You can see which numbers you need to use by running id -u and id -g.

SSL certificate

We’re going to set up certbot with a new SSL certificate from LetsEncrypt. Since I host other things on this server besides matrix.kylrth.com, I do a wildcard certificate (*.kylrth.com), which requires DNS verification by creating a TXT record in the DNS settings for my domain.You can use host -t txt _acme-challenge.kylrth.com to make sure the TXT records have been updated before having LetsEncrypt check them.

mkdir -p data/certbot/conf
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem \
    > data/certbot/conf/ssl-dhparams.pem
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf \
    > data/certbot/conf/options-ssl-nginx.conf
docker-compose run --rm certbot certonly --manual --preferred-challenges=dns \
    -w /var/www/certbot --email name@example.com -d '*.kylrth.com' \
    --rsa-key-size 4096 --agree-tos --force-renewal

If you don’t need a wildcard certificate, replace that last line with this:

docker-compose run --rm certbot certonly --webroot \
    -w /var/www/certbot --email name@example.com -d 'matrix.kylrth.com' \
    --rsa-key-size 4096 --agree-tos --force-renewal

To renew the wildcard certificate, you’ll have to run that last docker-compose run certbot line again. I’d suggest creating a scripts/ directory for things like this:

mkdir -p scripts
wget https://kylrth.com/post/matrix-setup/scripts/update_cert.sh \
    -O scripts/update_cert.sh
chmod +x scripts/update_cert.sh

To set the certbot container to renew a non-wildcard certificate, remove the profiles section from the certbot container in the docker-compose.yml so that the container runs with the rest of the services, and add the following instead:

entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

Note that changing the entrypoint this way will break the previous docker-compose run command that you ran to get the certificate if you were to run it again.

NGINX

To set up NGINX, download the app.conf I’ve got here (and modify it to your liking):

wget https://kylrth.com/post/matrix-setup/app.conf \
    -O data/nginx/app.conf

Synapse

Before doing anything else, make sure you have the version you want in the docker-compose config for the Synapse container. The last version to support ARM was v1.26.0, so if you’re on ARM you’ll need to use that. Otherwise, update the docker-compose config to use whatever version is latest.In general it’s not a good idea to use the default latest tag for remote images, because version upgrades often require changes to be made. You can see the upgrade instructions for Synapse here.

Now generate the initial configuration for Synapse:

docker-compose run --rm \
    -e SYNAPSE_SERVER_NAME=kylrth.com -e SYNAPSE_REPORT_STATS=no \
    synapse generate

Now edit data/synapse/homeserver.yaml to your liking. Here are some things I did:

Run docker-compose run -v /etc/passwd:/etc/passwd:ro postgres to give Postgres the chance to initialize the database before Synapse looks for it.

The next thing you should do is create the first user for yourself, and make that user an admin on the server. Run docker-compose up synapse to start the container temporarily, then open another terminal and run docker-compose exec synapse register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008. Don’t forget to make yourself an admin! Once you finish with that command, quit the up process with Ctrl+C, and then run docker-compose down to destroy all the containers.

Maubot

Generate a config file by running docker-compose run --rm maubot and then update the config to your liking, making sure to update the following objects:

server:
  public_url: https://matrix.kylrth.com
registration_secrets:
  kylrth.com:
    url: https://matrix.kylrth.com
    secret: <registration secret from data/synapse/homeserver.yaml>
admins:
  root: ''  # disable login
  <username>: <password>  # password will be replaced with bcrypted hash on startup

matrix-registration

Copy the sample config from the repo:

wget https://raw.githubusercontent.com/ZerataX/matrix-registration/master/config.sample.yaml \
    -O data/registration/config.yaml

Update the config to your liking, making sure to update the following elements:

server_location: 'http://synapse'
server_name: 'kylrth.com'
registration_shared_secret: <registration secret from data/synapse/homeserver.yaml>
admin_api_shared_secret: <invent a secret string here>
base_url: ''
db: 'sqlite:////data/db.sqlite3'
host: '0.0.0.0'  # super important
logging:
  handlers:
    file:
      filename: /data/m_reg.log

I’ve got a script that can be used to create a one-time-use token that expires after 7 days:

mkdir -p scripts
wget https://kylrth.com/post/matrix-setup/scripts/new_matrix_token.sh \
    -O scripts/new_matrix_token.sh
chmod +x scripts/new_matrix_token.sh

Read about your options for managing tokens here.

enabling the services with Systemd

See this section of my self-hosting post to learn how to use Systemd to make this set of containers a background service that starts at boot. Now we’re finally ready to get everything started.

sudo systemctl start services

Message me @kyle:kylrth.com and tell me how it went!