by Sam Hadow
In this blog post I’ll describe how to self host a matrix.org instance using podman. The homeserver used will be synapse.
First a short introduction:
matrix.org is an open source, secure and decentralized communication protocol. Secure because it defines how end to end encryption should be implemented in the clients.
With matrix.org you have homeservers and clients. Homeservers are what the clients connect to and can federate with each other (meaning someone with an account on homeserver A can talk to someone with an account on homeserver B).
Bridges to other messaging services can also be hosted alongside a homeserver. Hosting your own homeserver allows you to host the bridges you want.
In this blog post I explain how to self host a specific homeserver, which is maintained by the matrix.org foundation: synapse.
Create the database secret not to expose it in your configuration files:
echo -n "<postgres-pass>" > /tmp/secret
podman secret create synapse_postgres_pass /tmp/secret
Adapt the path to your own path for the directories:
mkdir -p /home/data/podman/synapse/{db,config,media,logs}
(adapt your-domain to your own, for example: example.org)
podman pod create --name synapse -p 8008:8008 -m=2048m
podman run -it --rm \
-v /home/data/podman/synapse/config:/data:Z \
-e SYNAPSE_SERVER_NAME=<your-domain> \
-e SYNAPSE_REPORT_STATS=no \
docker.io/matrixdotorg/synapse:latest generate
The previous command will generate a homeserver.yaml file. You’ll have to modify this file before using synapse. You’ll have to modify at least the following parts:
database:
name: psycopg2
txn_limit: 10000
args:
user: synapse
password: <POSTGRES_PASSWORD>
dbname: synapse
host: 127.0.0.1
port: 5432
cp_min: 5
cp_max: 10
enable_registration to false unless you want users to register freely on your instance.registration_shared_secret_path to have access to an API to create users. Be sure to use a secure secret as anyone having this secret can register on your instance as an admin. Also keep in mind the path is relative to the container, not the host.In this guide the port used is 8008, we’ll use nginx to server the synapse homeserver on the port 443.
(adapt your-domain to your own)
This part is important for users to have the name user@your-domain while hosting synapse on a subdomain. And it’s also important for clients and other homeserver to recognize your server.
in sites-available:
# synapse.conf
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name synapse.<your-domain>;
ssl_certificate /etc/letsencrypt/live/<your-domain>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<your-domain>/privkey.pem;
location /.well-known/matrix/server {
return 200 '{"m.server": "synapse.<your-domain>:443"}';
add_header Content-Type application/json;
}
location /.well-known/matrix/client {
return 200 '{"m.homeserver": {"base_url": "https://synapse.<your-domain>"},"m.identity_server": {"base_url": "https://vector.im"}}';
add_header Content-Type application/json;
add_header "Access-Control-Allow-Origin" *;
}
location / {
proxy_pass http://127.0.0.1:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
# Nginx by default only allows file uploads up to 1M in size
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
client_max_body_size 256M;
# Synapse responses may be chunked, which is an HTTP/1.1 feature.
proxy_http_version 1.1;
}
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
In your http.conf add this snippet in the https server block:
location /.well-known/matrix/server {
return 200 '{"m.server": "synapse.<your-domain>:443"}';
add_header Content-Type application/json;
add_header "Access-Control-Allow-Origin" *;
}
location /.well-known/matrix/client {
return 200 '{"m.homeserver": {"base_url": "https://synapse.<your-domain>"},"m.identity_server": {"base_url": "https://vector.im"}}';
add_header Content-Type application/json;
add_header "Access-Control-Allow-Origin" *;
}
Again adapt the path:
The database version can be different but it needs to be pinned to a specific version to avoid issues with updates. You’ll have to manually update this container.
podman run -d --pod=synapse \
--secret synapse_postgres_pass,type=env,target=POSTGRES_PASSWORD \
-e POSTGRES_DB="synapse" \
-e POSTGRES_USER="synapse" \
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --lc-collate=C --lc-ctype=C" \
-v /home/data/podman/synapse/db:/var/lib/postgresql/data:Z \
--name=synapse-db \
docker.io/library/postgres:16
podman run -d --pod=synapse \
-e SYNAPSE_CONFIG_PATH=/data/homeserver.yaml \
-v /home/data/podman/synapse/config:/data:Z \
-v /home/data/podman/synapse/media:/data/media:z \
-v /home/data/podman/synapse/logs:/data/logs:Z \
--name=synapse-app \
--label io.containers.autoupdate=registry docker.io/matrixdotorg/synapse:latest
Then you can generate the systemd services:
cd ~/.config/systemd/user/
podman generate systemd --restart-policy=on-failure --files --new --name synapse
systemctl --user daemon-reload
systemctl --user enable --now pod-synapse.service
Create these files in ~/.config/systemd/user/.
# pod-synapse.service
[Unit]
Description=Podman pod-synapse.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/run/user/1000/containers
Wants=container-synapse-app.service container-synapse-db.service
Before=container-synapse-app.service container-synapse-db.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStartPre=/usr/bin/podman pod create \
--infra-conmon-pidfile %t/pod-synapse.pid \
--pod-id-file %t/pod-synapse.pod-id \
--exit-policy=stop \
--name synapse \
-p 8008:8008 \
-m=2048m \
--replace
ExecStart=/usr/bin/podman pod start \
--pod-id-file %t/pod-synapse.pod-id
ExecStop=/usr/bin/podman pod stop \
--ignore \
--pod-id-file %t/pod-synapse.pod-id \
-t 10
ExecStopPost=/usr/bin/podman pod rm \
--ignore \
-f \
--pod-id-file %t/pod-synapse.pod-id
PIDFile=%t/pod-synapse.pid
Type=forking
[Install]
WantedBy=default.target
# container-synapse-db.service
[Unit]
Description=Podman container-synapse-db.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-synapse.service
After=pod-synapse.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--pod-id-file %t/pod-synapse.pod-id \
--sdnotify=conmon \
--replace \
-d \
--secret synapse_postgres_pass,type=env,target=POSTGRES_PASSWORD \
-e POSTGRES_DB=synapse \
-e POSTGRES_USER=synapse \
-e "POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C" \
-v /home/data/podman/synapse/db:/var/lib/postgresql/data:Z \
--name=synapse-db \
docker.io/library/postgres:16
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
-f \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
# container-synapse-app.service
[Unit]
Description=Podman container-synapse-app.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-synapse.service
After=pod-synapse.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--pod-id-file %t/pod-synapse.pod-id \
--sdnotify=conmon \
--replace \
-d \
-e SYNAPSE_CONFIG_PATH=/data/homeserver.yaml \
-v /home/data/podman/synapse/config:/data:Z \
-v /home/data/podman/synapse/media:/data/media:z \
-v /home/data/podman/synapse/logs:/data/logs:Z \
--name=synapse-app \
--label io.containers.autoupdate=registry docker.io/matrixdotorg/synapse:latest
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
-f \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
With access to the server, creating the user interactively:
podman exec -it synapse-app /bin/sh
register_new_matrix_user --user <username> --config /data/homeserver.yaml
Or with the registration API, you can use this script.
tags: messaging - podman - sysadmin