Prerequisites
See Part 1 to get a running Synapse server based off a Docker-Compose file, example file included at the bottom of this post
Matrix-Signal Bridge
Alright, assuming you’re up to date with part 1 then a Synapse server is running and accessible. Huge first step! Now to integrate a Signal bridge. This is going to be very similar to the WhatsApp bridge set up that is upcoming in part 3.
- Start a Signal daemon like Signald
docker-compose up -d signald
- update the
docker-compose.yaml
to contain an actual password forsignal-bridge-db
- use an ephemeral Docker container to create the bridge’s configuration file
$ docker run -it --rm -v signal-bridge-data:/data dock.mau.dev/tulir/mautrix-signal:latest
Didn't find a config file.
Copied default config file to /data/config.yaml
Modify that config file to your liking.
Start the container again after that to generate the registration file.
- Same as for Synapse, we’ll update this configuration file
sudo nano $(docker inspect --format='{{.Mountpoint}}' signal-bridge-data)/config.yaml
- Update a whole bunch of stuff in here:
- under
homeserver
- update
address
to behttp://synapse:8008
- update
domain
to be the domain you are ultimately going to use, for me it’snuc.localdomain
- update
- under
appservice
- update
address
tohttp://signal-bridge:29328
- update
database
to the full Postgres database string, e.g.postgres://mautrix_signal:reallyStrongPassword!2@signal-bridge-db/signal_bridge_db
- update
- under
signal
- update
socket_path
to/signald/signald.sock
- update
outgoing_attachment_dir
to/signald/attachments
- update
avatar_dir
to/signald/avatars
- update
data_dir
to/signald/data
- update
- under
bridge
- under
permission
add yourself as an administrator (e.g."toby@nuc.localdomain": "admin"
)
- under
- under
- ensure you save the file!
- use another ephemeral Docker container to consume the bridge’s configuration and create a registration file that can be used with Synapse
$ docker run -it --rm -v signal-bridge-data:/data dock.mau.dev/tulir/mautrix-signal:latest
Registration generated and saved to /data/registration.yaml
- Now we need to get the
registration.yaml
for this bridge into Syanpse. Luckily, it’s in a volume that is mounted to both containers, so it’s straightforward. Edit thehomeserver.yaml
sudo nano $(docker inspect --format='{{.Mountpoint}}' synapse-data)/homeserver.yaml
- find
app_service_config_files
(it’s likely commented out). Uncomment it and add the path to the the generatedconfig.yaml
# A list of application service config files to use
#
app_service_config_files:
- /signal-bridge/registration.yaml
- Now you should be able to start both the Signal bridge and Synapse. Note that it may error out a couple times as both are trying to start up talking to one another.
- Now is time to actually set up your Signal account with the Signal daemon. These instructions are basically from here.
- Invite (or send a direct message, depending on your client) the Signal bot which will be based on your own server name: (
@signalbot:nuc.localdomain
). You’ll get early feedback that it’ll work when you see the icon change to be Signal’s
- Invite (or send a direct message, depending on your client) the Signal bot which will be based on your own server name: (

- send the message
link
to the bridge bot, then in Signal go to theSettings
thenLinked devices
andAdd
- Scan the QR code, and hopefully you should be on your way!

At this point, when you receive a Signal message it should show upon the daemon and get pushed to Matrix. If this the first message you’ve received from that contact, you’ll be invited to a new chat (or new group, if it’s a group message) and then be able to chat away! A logical next double-puppeting, which makes the whole experience nicer. I have yet to find a way to proactively crawl existing conversations so they’re present in Matrix right off the bat.
Example docker-compose.yaml
version: "3.7"
volumes:
# We need to do some initial set up outside of Docker-Compose, so make these volumes external
synapse-data:
external: true
whatsapp-bridge-data:
external: true
signal-bridge-data:
external: true
# The Signal daemon (Signald) and the Signal bridge (Mautrix-Signal) need to share a volume
signald-data:
# Use these named volumes just for convenience
signal-bridge-db:
synapse-db:
services:
synapse-db:
container_name: synapse-db
image: postgres:13-alpine
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: <GENERATE A PASSWORD HERE> # Make sure to update this!
POSTGRES_DB: synapse_db
# ensure the database gets created correctly
# https://github.com/matrix-org/synapse/blob/master/docs/postgres.md#set-up-database
POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C
volumes:
- synapse-db:/var/lib/postgresql/data
ports:
- 5435:5432/tcp
synapse:
container_name: synapse
image: docker.io/matrixdotorg/synapse:latest
# Since synapse does not retry to connect to the database, restart upon failure
restart: unless-stopped
environment:
- SYNAPSE_CONFIG_PATH=/data/homeserver.yaml
volumes:
- synapse-data:/data
- signal-bridge-data:/signal-bridge
- whatsapp-bridge-data:/whatsapp-bridge
depends_on:
- synapse-db
ports:
- 8008:8008/tcp
- 8448:8448/tcp
whatsapp-bridge:
container_name: whatsapp-bridge
image: dock.mau.dev/tulir/mautrix-whatsapp:latest
restart: unless-stopped
volumes:
- whatsapp-bridge-data:/data
ports:
- 29318:29318/tcp
# Signal and bridge
signald:
container_name: signald
image: finn/signald:latest
restart: unless-stopped
volumes:
- signald-data:/signald
signal-bridge-db:
container_name: signal-bridge-db
image: postgres:13-alpine
restart: unless-stopped
environment:
POSTGRES_USER: mautrix_signal
POSTGRES_DB: signal_bridge_db
POSTGRES_PASSWORD: <GENERATE A PASSWORD HERE> # Make sure to update this!
volumes:
- signal-bridge-db:/var/lib/postgresql/data
ports:
- 5434:5432/tcp
signal-bridge:
container_name: signal-bridge
image: dock.mau.dev/tulir/mautrix-signal
restart: unless-stopped
volumes:
- signal-bridge-data:/data
- signald-data:/signald
ports:
- 29328:29328/tcp
depends_on:
- signal-bridge-db
- signald