Matrix Synapse on Kubernetes with Kapitan
Synapse 1.164.2, the Matrix homeserver, with federation and open registration off and its first admin registered by a Job, with Element Web and OpenID Connect as options, rendered by the kadet-hub kubernetes generator 1.14.0 from Synapse’s image. The database is the included recipe postgresql.
- Latest version:
1.0.0, published - Source: container image
ghcr.io/element-hq/synapse:v1.164.2@sha256:f0df87cff259872637b4b5e5174b9977e5e3046dd421505407803b5e45c5f842, recipe designed in kadet-hub - Proof level:
deploys, deployed on kind and smoke tested by the kadet-hub/recipes CI renderspassed in the release run of with check 0.18.0, kind v0.33.0- Output capabilities: none
- Inventory backends:
omegaconf - Prerequisites, installed once per cluster before the recipe:
cnpg - Generators: kubernetes 1.14.0
sha256:787129e1eaed6b9bc868fe4c4aec58d40de6a89fc3cc08424a4cb5822b855080 - Image security: grade F 434 findings, one per advisory and package, in the 2 images of version 1.0.0: CRITICAL 5, HIGH 111, MEDIUM 159, LOW 159, UNKNOWN 0. Scanned 2026-11-07 11:47 UTC. Full report
The grade rates the upstream images the recipe pins, not the recipe:ghcr.io/element-hq/element-web,ghcr.io/element-hq/synapse.
What it deploys
recipes.matrix-synapse(init.yml)recipes.matrix-synapse.synapse(synapse.yml):matrix-synapserecipes.matrix-synapse.admin(admin.yml):matrix-synapse-adminrecipes.matrix-synapse.element-web(element-web.yml), an option:matrix-synapse-element-webrecipes.matrix-synapse.federation(federation.yml), an optionrecipes.matrix-synapse.oidc(oidc.yml), an option:matrix-synapse
Required parameters
applications.matrix-synapse.server_name is the domain in every user ID
and room alias, such as example.com in @admin:example.com. It cannot
change later: Synapse refuses to start on a database that holds users of
another server name.
applications.matrix-synapse.registration_shared_secret lets
register_new_matrix_user and the admin API register accounts, admins
included, and also signs Synapse’s macaroons. Set it to a long random
string, such as one that openssl rand -hex 32 prints.
applications.matrix-synapse.admin.username and
applications.matrix-synapse.admin.password are the localpart and the
password of the first admin. The Job matrix-synapse-admin registers the
account with register_new_matrix_user --exists-ok once Synapse answers,
and leaves an existing account as it is. A later change of the password
does not reach the account. Change it in a client or through the admin API.
The option oidc needs applications.matrix-synapse.oidc.issuer, the
issuer URL of the OpenID Connect provider, such as a Keycloak realm, and
applications.matrix-synapse.oidc.client_id and
applications.matrix-synapse.oidc.client_secret, Synapse’s client at the
provider.
Set the shared secret, the password and the client secret as refs with
the backend of your choice. Without one of the parameters the compile
stops with Interpolation key '<key>' not found. postgresql requires no
parameter.
Include
A target includes the recipe and its options as classes and can override anything the recipe sets:
classes:
- recipes.matrix-synapse
- recipes.matrix-synapse.element-web # Element Web
- recipes.matrix-synapse.federation # federation with other servers
- recipes.matrix-synapse.oidc # login through OpenID Connect
parameters:
namespace: matrix-synapse
kapitan:
vars:
target: matrix-synapse
applications:
matrix-synapse:
server_name: example.com
registration_shared_secret: <ref>
admin:
username: admin
password: <ref>
oidc:
issuer: https://keycloak.example.com/realms/example
client_id: synapse
client_secret: <ref>
Fetching it
The quickstart goes through these steps on a kind cluster with the uptime-kuma recipe, from installing Kapitan to the running app.
A separate fetch inventory pins the recipe by version and digest. This is fetch/targets/recipes.yml for version 1.0.0:
parameters:
kapitan:
dependencies:
- type: oci
source: ghcr.io/kadet-hub/recipes/matrix-synapse:1.0.0@sha256:53c575c0f9d62a0b9506c2aa98fb63f35f1f6a00362c4c6a6eb2abdf7e85d885
output_path: inventory/classes/recipes/matrix-synapse
Version 1.0.0 includes postgresql 1.1.1. The consumer workflow adds the included recipes at the digests the index lists. Kapitan's --fetch reads no index, so for a local compile pull them by digest first:
tmp=$(mktemp -d)
oras pull -o "$tmp" ghcr.io/kadet-hub/recipes/postgresql@sha256:1e8465058c256cc86995dc8d6f1eb2f095df244fadcaf756935599f4d9ead6a9
mkdir -p inventory/classes/recipes/postgresql
tar -xf "$tmp/postgresql.tar" -C inventory/classes/recipes/postgresql --no-same-owner
The first command fetches the recipe into inventory/classes/recipes/matrix-synapse, the second the generators and compiles:
kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch
The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of the recipe and of the recipes it includes:
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/matrix-synapse@sha256:53c575c0f9d62a0b9506c2aa98fb63f35f1f6a00362c4c6a6eb2abdf7e85d885 \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/postgresql@sha256:1e8465058c256cc86995dc8d6f1eb2f095df244fadcaf756935599f4d9ead6a9 \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.
Settings
<s> stands for components.matrix-synapse, <w> for
components.matrix-synapse-element-web.
| Setting | Inventory |
|---|---|
URL clients reach Synapse at, https://<server_name>/ |
applications.matrix-synapse.public_baseurl, such as https://matrix.example.com/ when Synapse has a host of its own |
| Synapse version | <s>.image, tag and digest |
| Element Web version | <w>.image, tag and digest |
| other Synapse settings | a key <name>.yaml in <s>.config_maps.config.data with the settings as its value. Synapse reads the files in name order, and a later file replaces a top-level key of an earlier one, so keep each top-level key in one file. |
further keys of Element Web’s config.json |
<w>.config_maps.config.data.config\.json.value |
size of the claim data, 10Gi |
<s>.volume_claims.data.spec.resources.requests.storage |
| resources | <s>.resources, <w>.resources |
| exposure | a route or Ingress of the target to the Service matrix-synapse on port 8008, and to matrix-synapse-element-web on port 80 |
Synapse reads its configuration only when it starts, so after a change
restart it with kubectl rollout restart statefulset/matrix-synapse.
Synapse reads the database password from postgresql-cluster-app as
PGPASSWORD and connects with sslmode=require, which encrypts the
connection without verifying the server’s certificate. Synapse requires
the collation C for its database, which CloudNativePG gives a new
Cluster by default.
The Job matrix-synapse-admin waits in an init container until Synapse
answers through its Service, and goes 10 minutes after it finished.
Applying a new version of the recipe creates it again, and it changes
nothing for an existing admin.
Any list set in the target is appended to the recipe’s list. To replace
one, set the key to ~ in a class included after the recipe and the new
list in the target.
Proof
check matrix-synapse --level deploys with tests/target.yml, on kind for
each Kubernetes minor CI tests, with cnpg installed first. An image has
no Kubernetes objects to compare with, so the recipe is proven by
deploying it (RCP-44). tests/target.yml includes the options
element-web and oidc, runs one PostgreSQL instance and sets
public_baseurl to the Service matrix-synapse, so that the test reaches
the homeserver at the URL Element Web names. Its OpenID Connect issuer
does not resolve.
tests/smoke checks that the pods pass Pod Security restricted. Through
the Service, Synapse reports the version of the image tag, answers the
federation API and the server keys with 404, and a registration with 403.
The admin of the Job logs in, creates a second user, a room with that user
invited, an upload and a message. The second user joins and reads the
message and the upload, again after the test deleted the pod
matrix-synapse-0, with the same signing key on the claim. The login
flows name the provider of the option oidc. Element Web answers through
its Service, and the homeserver answers at the URL its config.json
names. In Chrome, driven through ChromeDriver, Element Web’s login page
names example.com and the provider Single sign-on, which it read from
the homeserver at that URL. GitHub’s runners bring Chrome and
ChromeDriver. Elsewhere set CHROMEDRIVER to a ChromeDriver of the
installed Chrome’s version. A login through an OpenID Connect provider is
not tested.
check deploys one target, so the option federation is proven by hand
on kind. Two targets with the option federated, and with the option off
on one of them the join got 404 (RCP-362 in
docs/specs/recipes.md).
Versions
1.0.0, :sha256:53c575c0f9d62a0b9506c2aa98fb63f35f1f6a00362c4c6a6eb2abdf7e85d885
The recipe's files are in kadet-hub/recipes. All recipes