Illustrative example: Imagine a small volunteer group that wants its own Matrix homeserver at chat.example.com. This is a hypothetical setup, not a report of a server that was tested. The same steps can be adapted to a real domain, but choose the Matrix server name carefully: Synapse uses it in user IDs, and it cannot be changed later.
This guide creates a Synapse service and PostgreSQL database with Docker Compose. Synapse’s official Docker image starts with SQLite by default, which is convenient for a throwaway trial; Synapse’s Docker documentation recommends PostgreSQL for production. The example therefore generates Synapse’s configuration first, changes its database settings to PostgreSQL, and then starts both containers. The official image is ghcr.io/element-hq/synapse. Instructions and image details were checked against the current official documentation on October 6, 2026; image tags and supported software versions can change, so review the linked documentation before a long-lived deployment.
What you need before installing
- A Linux server or another Docker-supported host with Docker Engine and the Docker Compose plugin installed. Confirm Compose is available with
docker compose version. Follow Docker’s official installation instructions for your operating system.
- A domain or subdomain that points to the server. In this example,
chat.example.com is the Synapse server name, so local user IDs will look like @alex:chat.example.com. Replace it with a domain you control before generating the configuration.
- A reverse proxy or another TLS termination method for public use. Synapse’s default HTTP listener on port
8008 is not an Internet-facing HTTPS endpoint. This Compose example binds that port to the host’s loopback interface so a reverse proxy on the same machine can reach it.
The name in SYNAPSE_SERVER_NAME is not necessarily the URL of every client connection. If you want shorter IDs such as @alex:example.com while serving clients from chat.example.com, that requires a deliberate domain and discovery configuration. Decide this before creating accounts.
1. Create a working directory and database password
On the server, make a directory for the Compose project and persistent Synapse files:
mkdir -p ~/matrix-synapse/data
cd ~/matrix-synapse
Create a local .env file that Docker Compose will read. Generate a long random password using a password manager or a local secure generator, and put it after POSTGRES_PASSWORD=. Use letters and numbers for this example to avoid YAML quoting problems later. Do not commit this file to a public repository, and restrict its permissions:
umask 077
nano .env
The file should contain one line, for example POSTGRES_PASSWORD=replace-with-a-long-random-value. The text is a placeholder, not a password to reuse. Keep the password available for the Synapse database configuration in step 3.
2. Define the Synapse and PostgreSQL services
Create compose.yaml in the project directory:
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
POSTGRES_DB: synapse
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U synapse -d synapse"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
synapse:
image: ghcr.io/element-hq/synapse:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
SYNAPSE_SERVER_NAME: chat.example.com
SYNAPSE_REPORT_STATS: "no"
volumes:
- ./data:/data
ports:
- "127.0.0.1:8008:8008"
volumes:
postgres_data:
Replace chat.example.com with the final server name you chose. The PostgreSQL health check lets Compose wait for the database to report ready before starting Synapse. The named database volume and the host-mounted ./data directory survive container replacement; removing containers alone does not erase them. Avoid docker compose down -v unless you intentionally want to delete the database volume.
The example uses :latest to make the first pull straightforward. For a maintained server, select and record a specific Synapse release tag or image digest, then update it through a planned upgrade process after reviewing the release notes. Do not assume the mutable latest tag will keep the same version.
3. Generate Synapse’s configuration and point it to PostgreSQL
Run the official image once in its configuration-generation mode. Because Compose mounts ./data at /data, the generated configuration and signing key stay on the host:
docker compose run --rm synapse generate
This command uses the server name and anonymous statistics choice from the Compose environment. Synapse also creates a signing key; keep it with the data directory and include it in protected backups. Do not regenerate or discard it during an ordinary container update.
Open data/homeserver.yaml and replace the generated database: section with the following settings. Use the exact password from .env in place of the placeholder:
database:
name: psycopg2
args:
user: synapse
password: 'replace-with-the-same-random-value'
dbname: synapse
host: postgres
port: 5432
cp_min: 5
cp_max: 10
Keep the surrounding YAML indentation and make sure there is only one active database: section. The hostname postgres is the Compose service name, which containers can resolve on their shared default network. The config uses Synapse’s documented PostgreSQL engine name, psycopg2, and database argument names including dbname.
For this small example, the database password appears in the Synapse configuration as well as the protected .env file. Treat both files as secrets, limit host access, and use a dedicated secrets-management approach if your deployment environment requires one. Do not paste the real password into logs, screenshots, or support requests.
4. Start Synapse with Docker Compose
Start the stack in the background:
docker compose up -d
Check the service state and read the startup logs:
docker compose ps
docker compose logs --tail=100 synapse
Wait for Synapse to complete its first-start database setup. A running container only means its process is active; inspect the logs for configuration, database, or permission errors before proceeding. To watch logs as they arrive, use docker compose logs -f synapse and press Ctrl+C to stop following them without stopping the container.
5. Verify the local API and configure public access
From the Docker host, request Synapse’s client API versions endpoint:
curl -fsS http://127.0.0.1:8008/_matrix/client/versions
A successful response is JSON describing supported Matrix client API versions. This confirms that the HTTP listener is responding locally; it does not prove that DNS, HTTPS, or federation are configured correctly.
Configure the host reverse proxy to forward requests for the chosen domain to http://127.0.0.1:8008 and obtain a valid TLS certificate. Preserve the Matrix request path when proxying. Then check the public endpoint:
curl -fsS https://chat.example.com/_matrix/client/versions
Replace the example hostname with your real one. If clients need to discover the homeserver from a separate user-ID domain, publish the appropriate Matrix well-known discovery records for that design. Federation also requires correct public routing and DNS; opening local port 8008 alone does not provide a complete public deployment. Review Synapse’s reverse-proxy and federation guidance before exposing the service.
6. Create the first administrator account
Synapse disables public registration by default. For a private server, keep that default and create accounts from the command line. Temporarily add a strong registration_shared_secret value to data/homeserver.yaml, then restart Synapse so it reads the change:
docker compose restart synapse
Run the registration tool inside the container. It prompts for a username and password; setting --admin creates the first administrator:
docker compose exec synapse register_new_matrix_user \
http://localhost:8008 \
-c /data/homeserver.yaml \
--admin
After the account is created, remove registration_shared_secret from the configuration and restart Synapse again. Leaving the secret configured allows anyone who obtains it to register standard or administrator accounts through the registration interface. Sign in through a Matrix client that supports custom homeservers, using the server URL provided by your reverse proxy.
Backups, upgrades, and common fixes
- Back up the right data: stop or consistently snapshot the PostgreSQL data and back up
./data, including configuration, media, and signing key. A copy of compose.yaml alone cannot restore the homeserver. Store backups somewhere separate from the server and periodically confirm you can read them.
- Synapse cannot write to
/data: inspect host ownership and permissions on ./data. The official image’s runtime user needs permission to read configuration and write data. Adjust host ownership carefully rather than making the directory world-writable.
- Database connection fails: check that the database config uses
host: postgres, that the password matches .env, and that docker compose logs postgres shows a healthy database. Compose’s depends_on readiness condition reduces startup races but does not repair incorrect credentials.
- Local API works but clients cannot connect: check the DNS record, TLS certificate, reverse-proxy upstream, and discovery records for your chosen domain arrangement. The loopback-only port mapping is intentional for a same-host proxy; a proxy in another container needs a shared Docker network and a different routing design.
- Upgrading: back up first, review the Synapse release notes, update the pinned image tag, and recreate the service with
docker compose pull followed by docker compose up -d. Keep the same data directory, database volume, server name, and signing key.
In the illustrative volunteer-group setup, installation is complete only when the local versions endpoint responds, the HTTPS endpoint responds through the proxy, and the administrator can sign in using the intended Matrix ID domain. Those checks are practical indicators of a working first deployment; they are not a substitute for reviewing security, backups, federation, and operational requirements before inviting a wider community.
Official references