# Matrix Synapse + Element Web # PostgreSQL 16 + Synapse homeserver + Element Web client # ArgoCD managed - BlueJay Lab # DB credentials sourced from 1Password via OnePasswordItem CRD (matrix-credentials) # Synapse homeserver.yaml DB password injected at runtime via init container --- apiVersion: v1 kind: Namespace metadata: name: matrix labels: app.kubernetes.io/part-of: bluejay-infra --- # Synapse homeserver.yaml template ConfigMap # DB password placeholder __DB_PASSWORD__ is replaced at pod startup by init container apiVersion: v1 kind: ConfigMap metadata: name: synapse-config namespace: matrix data: homeserver.yaml.template: | server_name: "iamworkin.lan" pid_file: /data/homeserver.pid public_baseurl: "https://matrix.iamworkin.lan/" listeners: - port: 8008 tls: false type: http x_forwarded: true bind_addresses: ["0.0.0.0"] resources: - names: [client, federation] compress: false database: name: psycopg2 args: user: __DB_USER__ password: __DB_PASSWORD__ database: synapse host: matrix-postgres port: 5432 cp_min: 5 cp_max: 10 log_config: "/config/log.config" media_store_path: /data/media_store registration_shared_secret: "a208f2e4b260f6b7d6ff4566df49c56c8b73fa20b911ce4e617b791ee7868adc" report_stats: false macaroon_secret_key: "9964f398e8b48a91469ad419d293c06db4562f49df8cc6e129fb3a801fd9052d" form_secret: "7b0a9dbaf9ee94450e0b3271c408dfc4d313a55843ce4eec2ac1bb0315ffeb76" signing_key_path: "/data/signing.key" trusted_key_servers: - server_name: "matrix.org" enable_registration: false suppress_key_server_warning: true log.config: | version: 1 formatters: precise: format: "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s" handlers: console: class: logging.StreamHandler formatter: precise loggers: synapse.storage.SQL: level: WARNING root: level: WARNING handlers: [console] disable_existing_loggers: false --- # PostgreSQL 16 StatefulSet # Credentials from 1Password-synced matrix-credentials secret apiVersion: apps/v1 kind: StatefulSet metadata: name: matrix-postgres namespace: matrix labels: app: matrix-postgres spec: serviceName: matrix-postgres replicas: 1 selector: matchLabels: app: matrix-postgres template: metadata: labels: app: matrix-postgres spec: containers: - name: postgres image: postgres:16-alpine ports: - containerPort: 5432 name: postgres env: - name: POSTGRES_USER valueFrom: secretKeyRef: name: matrix-credentials key: DB-User - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: matrix-credentials key: DB-Password - name: POSTGRES_DB value: synapse - name: POSTGRES_INITDB_ARGS value: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" volumeMounts: - name: matrix-postgres-data mountPath: /var/lib/postgresql/data subPath: pgdata resources: requests: memory: 256Mi cpu: 100m limits: memory: 1Gi cpu: 500m livenessProbe: exec: command: ["pg_isready", "-U", "synapse"] initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: exec: command: ["pg_isready", "-U", "synapse"] initialDelaySeconds: 5 periodSeconds: 5 volumeClaimTemplates: - metadata: name: matrix-postgres-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 5Gi --- apiVersion: v1 kind: Service metadata: name: matrix-postgres namespace: matrix spec: selector: app: matrix-postgres ports: - port: 5432 targetPort: 5432 name: postgres clusterIP: None --- # Synapse Data PVC apiVersion: v1 kind: PersistentVolumeClaim metadata: name: synapse-data namespace: matrix spec: accessModes: [ReadWriteOnce] resources: requests: storage: 2Gi --- # Synapse Deployment # Init container injects DB credentials from 1Password secret into homeserver.yaml apiVersion: apps/v1 kind: Deployment metadata: name: synapse namespace: matrix labels: app: synapse spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: synapse template: metadata: labels: app: synapse spec: initContainers: - name: generate-signing-key image: matrixdotorg/synapse:latest securityContext: runAsUser: 0 command: ["sh", "-c"] args: - | if [ \! -f /data/signing.key ]; then python -m synapse.app.homeserver --generate-keys --config-path /config-template/homeserver.yaml.template 2>/dev/null || true # If key generation fails with template, create a minimal config for key gen if [ \! -f /data/signing.key ]; then echo server_name: iamworkin.lan > /tmp/minimal.yaml echo signing_key_path: /data/signing.key >> /tmp/minimal.yaml python -c "from signedjson.key import generate_signing_key, write_signing_keys; import sys; key = generate_signing_key(a_auto); write_signing_keys(open(/data/signing.key,w), [key])" 2>/dev/null || true fi fi chown 991:991 /data/signing.key 2>/dev/null || true chmod 644 /data/signing.key 2>/dev/null || true mkdir -p /data/media_store chown -R 991:991 /data 2>/dev/null || true volumeMounts: - name: synapse-data mountPath: /data - name: synapse-config-template mountPath: /config-template - name: inject-credentials image: busybox:latest command: ["sh", "-c"] args: - | # Copy template and substitute DB credentials from 1Password secret cp /config-template/log.config /config/log.config sed -e "s/__DB_PASSWORD__/${DB_PASSWORD}/g" \ -e "s/__DB_USER__/${DB_USER}/g" \ /config-template/homeserver.yaml.template > /config/homeserver.yaml echo "Credentials injected into homeserver.yaml" env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: matrix-credentials key: DB-Password - name: DB_USER valueFrom: secretKeyRef: name: matrix-credentials key: DB-User volumeMounts: - name: synapse-config-template mountPath: /config-template - name: synapse-config-rendered mountPath: /config containers: - name: synapse image: matrixdotorg/synapse:latest ports: - containerPort: 8008 name: http env: - name: SYNAPSE_CONFIG_DIR value: /config - name: SYNAPSE_CONFIG_PATH value: /config/homeserver.yaml volumeMounts: - name: synapse-data mountPath: /data - name: synapse-config-rendered mountPath: /config resources: requests: memory: 512Mi cpu: 200m limits: memory: 2Gi cpu: "1" livenessProbe: httpGet: path: /health port: 8008 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 8008 initialDelaySeconds: 30 periodSeconds: 5 volumes: - name: synapse-data persistentVolumeClaim: claimName: synapse-data - name: synapse-config-template configMap: name: synapse-config - name: synapse-config-rendered emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: synapse namespace: matrix spec: selector: app: synapse ports: - port: 8008 targetPort: 8008 name: http --- # Element Web ConfigMap apiVersion: v1 kind: ConfigMap metadata: name: element-web-config namespace: matrix data: config.json: | { "default_server_config": { "m.homeserver": { "base_url": "https://matrix.iamworkin.lan", "server_name": "iamworkin.lan" } }, "brand": "BlueJay Chat", "disable_guests": true, "disable_3pid_login": true } --- # Element Web Deployment apiVersion: apps/v1 kind: Deployment metadata: name: element-web namespace: matrix labels: app: element-web spec: replicas: 1 selector: matchLabels: app: element-web template: metadata: labels: app: element-web spec: enableServiceLinks: false containers: - name: element-web image: vectorim/element-web:latest ports: - containerPort: 80 name: http volumeMounts: - name: element-config mountPath: /app/config.json subPath: config.json resources: requests: memory: 32Mi cpu: 10m limits: memory: 128Mi cpu: 100m livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5 volumes: - name: element-config configMap: name: element-web-config --- apiVersion: v1 kind: Service metadata: name: element-web namespace: matrix spec: selector: app: element-web ports: - port: 80 targetPort: 80 name: http --- # TLS Certificates via cert-manager apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: matrix-tls namespace: matrix spec: secretName: matrix-tls issuerRef: name: step-ca-acme kind: ClusterIssuer dnsNames: - matrix.iamworkin.lan --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: element-tls namespace: matrix spec: secretName: element-tls issuerRef: name: step-ca-acme kind: ClusterIssuer dnsNames: - element.iamworkin.lan --- # Traefik IngressRoute - Synapse apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: synapse namespace: matrix spec: entryPoints: - websecure routes: - match: Host(`matrix.iamworkin.lan`) kind: Rule services: - name: synapse port: 8008 tls: secretName: matrix-tls --- # Traefik IngressRoute - Element Web apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: element-web namespace: matrix spec: entryPoints: - websecure routes: - match: Host(`element.iamworkin.lan`) kind: Rule services: - name: element-web port: 80 tls: secretName: element-tls --- # 1Password secret sync — creates matrix-credentials K8s Secret # Fields: DB-User, DB-Password, Registration-Secret, username, password, URL apiVersion: onepassword.com/v1 kind: OnePasswordItem metadata: name: matrix-credentials namespace: matrix spec: itemPath: vaults/IAmWorkin/items/Matrix Synapse --- # Public IngressRoute - Element Web (flowercore.io with Cloudflare origin cert) apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: element-public namespace: matrix spec: entryPoints: - websecure routes: - match: Host(`element.flowercore.io`) kind: Rule services: - name: element-web port: 80 tls: secretName: cf-origin-flowercore-io --- # Public IngressRoute - Synapse (flowercore.io with Cloudflare origin cert) apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: synapse-public namespace: matrix spec: entryPoints: - websecure routes: - match: Host(`matrix.flowercore.io`) kind: Rule services: - name: synapse port: 8008 tls: secretName: cf-origin-flowercore-io