Skip to content

Add Gateway/HTTPRoute alternative ingress#1050

Open
JoshuaHassler wants to merge 5 commits intoelement-hq:mainfrom
JoshuaHassler:main
Open

Add Gateway/HTTPRoute alternative ingress#1050
JoshuaHassler wants to merge 5 commits intoelement-hq:mainfrom
JoshuaHassler:main

Conversation

@JoshuaHassler
Copy link
Copy Markdown

@JoshuaHassler JoshuaHassler commented Feb 18, 2026

This adds support for the Kubernetes v1 Gateway spec as an alternative to the traditional Ingress resources. This logic supports using Ingresses as they are now, using HTTPRoutes only, or a mix. HTTPRoutes can be connected to an existing gateway, or a new gateway can be generated for the user. I tried to unify the template logic and reuse existing code wherever possible. Currently using this in production on my home server, and I have run what tests I could figure out how to locally 🙂

A few key notes:

  • The change does reorganize a few keys under the "ingress" config and is therefore technically a "breaking" change. I could restore these without affecting the HTTPRoute logic. Edit: I see you have deprication logic already so that is probably the way to handle any moving keys
  • defaults to ingress resources
  • Added an option to disable ingress altogether since I was writing the conditional logic anyway. This can be removed, but it is a common feature on Helm charts as an escape hatch for complex deployments.
  • I have intentionally not handled routing SFU via TCP/UDP Routes in this PR. I can add them, but they have mixed support within gateway controllers, and this PR was already getting long as is.

As an outside contributor, how should I handle the copyright notices? I don't see a CLA, but all the files are currently copyrighted by Element.
I am not sure what test fragments you may want me to add, since I intentionally tried to make it pass without changing the minimum config.

Thanks for all the work making ess-comunity possible, and I look forward to your review!

@JoshuaHassler JoshuaHassler requested a review from a team as a code owner February 18, 2026 22:11
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 18, 2026

CLA assistant check
All committers have signed the CLA.

@JoshuaHassler
Copy link
Copy Markdown
Author

It seems the CLAassistant has answered one of my questions 😄. I'll update the files.

@benbz
Copy link
Copy Markdown
Member

benbz commented Feb 20, 2026

👋 thanks for raising this PR. Sorry I meant to reply to this yesterday but it got put to one side and forgot to pick it back up.

Supporting the Gateway API is definitely something we would accept and would like in ESS Community. We would want to get the design & structure of the values correct given how fundamental this would be to the whole stack. I haven't looked at this PR in huge detail yet, but wanted to raise some initial queries. I don't necessarily know the correct answer yet to all of these but they're just things that came to mind on a first pass

  • Is there a reason to put the Gateway values in with the Ingress values?
    • i.e. have synapse.gateway.host as well as synapse.ingress.host use the present/absence of ...host to determine which to use?
    • Or pull synapse.ingress.host up to synapse.host and have your type value or enabled flags under synapse.{gateway,ingress}
  • When the chart creates its own Gateway are providing wildcard certs via a single value still possible?
    • I think they are due to the element-io.gateway.tlsConfig helper still using coalesce but want to confirm
    • Alternatively would your expectation be that if you need that setup you must provide an existing gateway?

Other observations

  • We'd want HTTPRoute and Gateway doing capability checks that those resources available in the cluster
  • There'd need to be similar tests to tests/manifests/test_ingresses.py for Gateway API resources
  • Are there other charts you've been looking at for inspiration on how to structure the values / what functionality to provide?
    • We're not going to blindly do what other charts do, especially given we have the constraint that we're a multi-component chart where all components are optional. Conversely, we're not going to do our own thing for the sake of it, so if there's an emerging convention for the structure of Gateway API related values we're open to that

I'll try to look at this in more detail next week, especially as we discuss the questions I've asked above. Once again, thanks for raising this

@github-actions
Copy link
Copy Markdown

dyff of changes in rendered templates of CI manifests

Full contents of manifests and dyffs are available in https://github.com/element-hq/ess-helm/actions/runs/22161565420/artifacts/5592879875

all-enabled-values.yaml
@@ Deployment/ess-ci/release-name-haproxy - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-authentication-service - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-rtc-authorisation-service - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-rtc-sfu - spec.template.spec @@
- restartPolicy: Always



@@ Ingress/ess-ci/release-name-synapse - metadata @@
+   test: true



@@ Job/ess-ci/release-name-deployment-markers-post - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ Job/ess-ci/release-name-deployment-markers-pre - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ Job/ess-ci/release-name-init-secrets - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ StatefulSet/ess-ci/release-name-hookshot - spec.template.spec @@
- restartPolicy: Always



@@ StatefulSet/ess-ci/release-name-postgres - spec.template.spec @@
- restartPolicy: Always



@@ StatefulSet/ess-ci/release-name-synapse-main - spec.template.spec @@
- restartPolicy: Always

element-admin-checkov-values.yaml
@@ ConfigMap/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- data:
-   default.conf: |
-     # Built from https://github.com/element-hq/element-admin/blob/main/docker/nginx.conf
-     # * /health added for k8s
-     # * setting a charset
-     # * setting error_page
-     # * listening on IPv6
-     # * setting server_name
-     # * adding 'Cache-Control: no-cache' to root
-     # * ensuring security headers are applied even where there's location blocks
-     server {
-       listen       8080;
-       listen  [::]:8080 ipv6only=on;
-       server_name  localhost;
- 
-       root   /dist;  # noqa
-       index  index.html;
-       charset utf-8;
- 
-       # Enable gzip compression
-       gzip on;
-       gzip_static on;
- 
-       # Cache static assets
-       location /assets {
-           expires 1y;
-           add_header Cache-Control "public, max-age=31536000, immutable";
-           include /etc/nginx/security_headers.conf;
-       }
- 
-       include /etc/nginx/security_headers.conf;
- 
-       # Set no-cache for the index.html
-       # so that browsers always check for a new copy of Element Admin.
-       # NB http://your-domain/ and http://your-domain/? are also covered by this
-       location / {
-           add_header Cache-Control "no-cache";
-           index /index.runtime.html /index.html;
-           try_files $uri $uri/ /;
-           include /etc/nginx/security_headers.conf;
-       }
- 
-       location = /health {
-           allow all;
-           default_type 'application/json';
-           return 200 '{"status": "ok"}';
-       }
-       # redirect server error pages to the static page /50x.html
-       #
-       error_page   500 502 503 504  /50x.html;
-     }
-   # Customisations that we do at the http rather than the server level
-   http_customisations.conf: |
-     server_tokens off;
-     set_real_ip_from 0.0.0.0/0;
-     set_real_ip_from ::/0;
-     real_ip_header X-Forwarded-For;
-   # For repeated inclusion in default.conf because the add_header directives need to be repeated as per
-   # https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header as they are only inherited from
-   # the server block iff there's no add_header directives in the location block
-   security_headers.conf: |
-     # Copyright 2025 New Vector Ltd
-     # Copyright 2025 Element Creations Ltd
-     # SPDX-License-Identifier: AGPL-3.0-only
- 
-     add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self'; connect-src *; object-src 'none'; media-src 'self'; child-src 'none'; worker-src 'self'; manifest-src 'self';" always;
-     add_header X-Content-Type-Options "nosniff" always;
-     add_header X-Frame-Options "DENY" always;
-     add_header X-Robots-Tag "noindex, nofollow, noarchive, noimageindex" always;
-     add_header X-XSS-Protection "1; mode=block" always;
-     add_header Referrer-Policy "strict-origin-when-cross-origin" always;
-     add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=()" always;


@@ Deployment/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/deployment.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
-   annotations:
-     checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-     checkov.io/skip2: CKV_K8S_43=No digests
-     checkov.io/skip3: CKV2_K8S_6=No network policy yet
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-     k8s.element.io/element-admin-config-hash: "6b669cd646bf3a19cda81458eb79e5295db17683"
-   name: release-name-element-admin
-   namespace: ess-ci
- spec:
-   replicas: 1
-   selector:
-     matchLabels:
-       app.kubernetes.io/instance: release-name-element-admin
-   strategy:
-     type: RollingUpdate
-     rollingUpdate:
-       maxSurge: 2
-       maxUnavailable: 0
-   template:
-     metadata:
-       labels:
-         app.kubernetes.io/managed-by: Helm
-         app.kubernetes.io/part-of: matrix-stack
-         app.kubernetes.io/component: matrix-admin-client
-         app.kubernetes.io/name: element-admin
-         app.kubernetes.io/instance: release-name-element-admin
-         app.kubernetes.io/version: "0.1.10"
-         k8s.element.io/element-admin-config-hash: "6b669cd646bf3a19cda81458eb79e5295db17683"
-       annotations:
-         checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-         checkov.io/skip2: CKV_K8S_43=No digests
-         checkov.io/skip3: CKV2_K8S_6=No network policy yet
-     spec:
-       automountServiceAccountToken: false
-       serviceAccountName: release-name-element-admin
-       securityContext:
-         fsGroup: 10104
-         runAsGroup: 10104
-         runAsNonRoot: true
-         runAsUser: 10104
-         seccompProfile:
-           type: RuntimeDefault
-         supplementalGroups: []
-       restartPolicy: Always
-       topologySpreadConstraints:
-         - labelSelector:
-             matchLabels:
-               app.kubernetes.io/instance: release-name-element-admin
-           matchLabelKeys:
-             - pod-template-hash
-           maxSkew: 1
-           topologyKey: kubernetes.io/hostname
-           whenUnsatisfiable: ScheduleAnyway
-       containers:
-         - name: element-admin
-           image: "oci.element.io/element-admin:0.1.10"
-           imagePullPolicy: Always
-           securityContext:
-             allowPrivilegeEscalation: false
-             capabilities:
-               drop:
-                 - ALL
-             readOnlyRootFilesystem: true
-           ports:
-             - containerPort: 8080
-               name: http
-               protocol: TCP
-           livenessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: http
-               scheme: HTTP
-           readinessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: http
-               scheme: HTTP
-           startupProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: http
-               scheme: HTTP
-           resources:
-             limits:
-               memory: 200Mi
-             requests:
-               cpu: 50m
-               memory: 50Mi
-           volumeMounts:
-             - mountPath: /tmp
-               name: nginx-tmp
-       volumes:
-         - emptyDir:
-             medium: Memory
-           name: nginx-tmp


@@ Ingress/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/ingress.yaml
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- spec:
-   tls:
-     - hosts:
-         - "admin.ess.localhost"
-   rules:
-     - host: "admin.ess.localhost"
-       http:
-         paths:
-           - path: /
-             pathType: Prefix
-             backend:
-               service:
-                 name: release-name-element-admin
-                 port:
-                   name: http


@@ Service/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/service.yaml
- apiVersion: v1
- kind: Service
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- spec:
-   type: ClusterIP
-   internalTrafficPolicy: Cluster
-   ipFamilyPolicy: PreferDualStack
-   ports:
-     - port: 8080
-       targetPort: http
-       name: http
-   selector:
-     app.kubernetes.io/instance: release-name-element-admin


@@ ServiceAccount/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/serviceaccount.yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- automountServiceAccountToken: false

element-admin-minimal-values.yaml
@@ ConfigMap/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- data:
-   default.conf: |
-     # Built from https://github.com/element-hq/element-admin/blob/main/docker/nginx.conf
-     # * /health added for k8s
-     # * setting a charset
-     # * setting error_page
-     # * listening on IPv6
-     # * setting server_name
-     # * adding 'Cache-Control: no-cache' to root
-     # * ensuring security headers are applied even where there's location blocks
-     server {
-       listen       8080;
-       listen  [::]:8080 ipv6only=on;
-       server_name  localhost;
- 
-       root   /dist;  # noqa
-       index  index.html;
-       charset utf-8;
- 
-       # Enable gzip compression
-       gzip on;
-       gzip_static on;
- 
-       # Cache static assets
-       location /assets {
-           expires 1y;
-           add_header Cache-Control "public, max-age=31536000, immutable";
-           include /etc/nginx/security_headers.conf;
-       }
- 
-       include /etc/nginx/security_headers.conf;
- 
-       # Set no-cache for the index.html
-       # so that browsers always check for a new copy of Element Admin.
-       # NB http://your-domain/ and http://your-domain/? are also covered by this
-       location / {
-           add_header Cache-Control "no-cache";
-           index /index.runtime.html /index.html;
-           try_files $uri $uri/ /;
-           include /etc/nginx/security_headers.conf;
-       }
- 
-       location = /health {
-           allow all;
-           default_type 'application/json';
-           return 200 '{"status": "ok"}';
-       }
-       # redirect server error pages to the static page /50x.html
-       #
-       error_page   500 502 503 504  /50x.html;
-     }
-   # Customisations that we do at the http rather than the server level
-   http_customisations.conf: |
-     server_tokens off;
-     set_real_ip_from 0.0.0.0/0;
-     set_real_ip_from ::/0;
-     real_ip_header X-Forwarded-For;
-   # For repeated inclusion in default.conf because the add_header directives need to be repeated as per
-   # https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header as they are only inherited from
-   # the server block iff there's no add_header directives in the location block
-   security_headers.conf: |
-     # Copyright 2025 New Vector Ltd
-     # Copyright 2025 Element Creations Ltd
-     # SPDX-License-Identifier: AGPL-3.0-only
- 
-     add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self'; connect-src *; object-src 'none'; media-src 'self'; child-src 'none'; worker-src 'self'; manifest-src 'self';" always;
-     add_header X-Content-Type-Options "nosniff" always;
-     add_header X-Frame-Options "DENY" always;
-     add_header X-Robots-Tag "noindex, nofollow, noarchive, noimageindex" always;
-     add_header X-XSS-Protection "1; mode=block" always;
-     add_header Referrer-Policy "strict-origin-when-cross-origin" always;
-     add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), magnetometer=(), accelerometer=(), gyroscope=()" always;


@@ Deployment/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/deployment.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-     k8s.element.io/element-admin-config-hash: "6b669cd646bf3a19cda81458eb79e5295db17683"
-   name: release-name-element-admin
-   namespace: ess-ci
- spec:
-   replicas: 1
-   selector:
-     matchLabels:
-       app.kubernetes.io/instance: release-name-element-admin
-   strategy:
-     type: RollingUpdate
-     rollingUpdate:
-       maxSurge: 2
-       maxUnavailable: 0
-   template:
-     metadata:
-       labels:
-         app.kubernetes.io/managed-by: Helm
-         app.kubernetes.io/part-of: matrix-stack
-         app.kubernetes.io/component: matrix-admin-client
-         app.kubernetes.io/name: element-admin
-         app.kubernetes.io/instance: release-name-element-admin
-         app.kubernetes.io/version: "0.1.10"
-         k8s.element.io/element-admin-config-hash: "6b669cd646bf3a19cda81458eb79e5295db17683"
-     spec:
-       automountServiceAccountToken: false
-       serviceAccountName: release-name-element-admin
-       securityContext:
-         fsGroup: 10104
-         runAsGroup: 10104
-         runAsNonRoot: true
-         runAsUser: 10104
-         seccompProfile:
-           type: RuntimeDefault
-         supplementalGroups: []
-       restartPolicy: Always
-       topologySpreadConstraints:
-         - labelSelector:
-             matchLabels:
-               app.kubernetes.io/instance: release-name-element-admin
-           matchLabelKeys:
-             - pod-template-hash
-           maxSkew: 1
-           topologyKey: kubernetes.io/hostname
-           whenUnsatisfiable: ScheduleAnyway
-       containers:
-         - name: element-admin
-           image: "oci.element.io/element-admin:0.1.10"
-           imagePullPolicy: Always
-           securityContext:
-             allowPrivilegeEscalation: false
-             capabilities:
-               drop:
-                 - ALL
-             readOnlyRootFilesystem: true
-           ports:
-             - containerPort: 8080
-               name: http
-               protocol: TCP
-           livenessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: http
-               scheme: HTTP
-           readinessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: http
-               scheme: HTTP
-           startupProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: http
-               scheme: HTTP
-           resources:
-             limits:
-               memory: 200Mi
-             requests:
-               cpu: 50m
-               memory: 50Mi
-           volumeMounts:
-             - mountPath: /tmp
-               name: nginx-tmp
-       volumes:
-         - emptyDir:
-             medium: Memory
-           name: nginx-tmp


@@ Ingress/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/ingress.yaml
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- spec:
-   tls:
-     - hosts:
-         - "admin.ess.localhost"
-   rules:
-     - host: "admin.ess.localhost"
-       http:
-         paths:
-           - path: /
-             pathType: Prefix
-             backend:
-               service:
-                 name: release-name-element-admin
-                 port:
-                   name: http


@@ Service/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/service.yaml
- apiVersion: v1
- kind: Service
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- spec:
-   type: ClusterIP
-   internalTrafficPolicy: Cluster
-   ipFamilyPolicy: PreferDualStack
-   ports:
-     - port: 8080
-       targetPort: http
-       name: http
-   selector:
-     app.kubernetes.io/instance: release-name-element-admin


@@ ServiceAccount/ess-ci/release-name-element-admin @@
- ---
- # Source: matrix-stack/templates/element-admin/serviceaccount.yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-admin-client
-     app.kubernetes.io/name: element-admin
-     app.kubernetes.io/instance: release-name-element-admin
-     app.kubernetes.io/version: "0.1.10"
-   name: release-name-element-admin
-   namespace: ess-ci
- automountServiceAccountToken: false

element-web-checkov-values.yaml
@@ ConfigMap/ess-ci/release-name-element-web-nginx @@
- ---
- # Source: matrix-stack/templates/element-web/nginx_configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web-nginx
-   namespace: ess-ci
- data:
-   default.conf: |
-     # Copy of https://github.com/element-hq/element-web/blob/v1.11.97/docker/nginx-templates/default.conf.template but
-     # * not as a template, using a hard-coded port
-     # * the recommendations from https://github.com/element-hq/element-web/tree/v1.11.97?tab=readme-ov-file#configuration-best-practices added
-     # * /health added for k8s
-     # * setting a charset
-     # * ensuring our security headers include are applied, whether or not paths are in location blocks or not
-     server {
-       listen       8080;
-       listen  [::]:8080 ipv6only=on;
-       server_name  localhost;
- 
-       root   /usr/share/nginx/html;  # noqa
-       index  index.html;
-       charset utf-8;
- 
-       include /etc/nginx/security_headers.conf;
- 
-       # Set no-cache for the version, config and index.html
-       # so that browsers always check for a new copy of Element Web.
-       # NB http://your-domain/ and http://your-domain/? are also covered by this
- 
-       location = /index.html {
-           add_header Cache-Control "no-cache";
-           include /etc/nginx/security_headers.conf;
-       }
-       location = /version {
-           add_header Cache-Control "no-cache";
-           include /etc/nginx/security_headers.conf;
-       }
-       # covers config.json and config.hostname.json requests as it is prefix.
-       location /config {
-           # Serving /app/config.json as per https://github.com/element-hq/element-web/blob/v1.11.97/docker/docker-entrypoint.d/18-load-element-modules.sh#L15
-           root /tmp/element-web-config;
-           add_header Cache-Control "no-cache";
-           include /etc/nginx/security_headers.conf;
-       }
-       location /modules {
-           alias /modules;
-       }
-       location = /health {
-           allow all;
-           default_type 'application/json';
-           return 200 '{"status": "ok"}';
-       }
-       # redirect server error pages to the static page /50x.html
-       #
-       error_page   500 502 503 504  /50x.html;
-     }
-   # Customisations that we do at the http rather than the server level
-   http_customisations.conf: |
-     server_tokens off;
-     set_real_ip_from 0.0.0.0/0;
-     set_real_ip_from ::/0;
-     real_ip_header X-Forwarded-For;
-   # For repeated inclusion in default.conf because the add_header directives need to be repeated as per
-   # https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header as they are only inherited from
-   # the server block iff there's no add_header directives in the location block
-   security_headers.conf: |
-     # Copyright 2025 New Vector Ltd
-     # Copyright 2025 Element Creations Ltd
-     # SPDX-License-Identifier: AGPL-3.0-only
- 
-     add_header Content-Security-Policy "frame-ancestors 'self'";
-     add_header X-Content-Type-Options nosniff;
-     add_header X-Frame-Options SAMEORIGIN;
-     add_header X-Robots-Tag "noindex, nofollow, noarchive, noimageindex";
-     add_header X-XSS-Protection "1; mode=block";


@@ ConfigMap/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- data:
-   config.json: |
-     {
-       "bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
-       "default_server_config": {
-         "m.homeserver": {}
-       },
-       "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
-       "mobile_guide_app_variant": "element",
-       "setting_defaults": {}
-     }


@@ Deployment/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/deployment.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
-   annotations:
-     checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-     checkov.io/skip2: CKV_K8S_43=No digests
-     checkov.io/skip3: CKV2_K8S_6=No network policy yet
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-     k8s.element.io/element-web-config-hash: "994c7bbea2846e7cd12de639bba56e30f0974be0"
-     k8s.element.io/nginx-config-hash: "ebf74e60cb1f5e455ca5140db2681c39df455fd6"
-   name: release-name-element-web
-   namespace: ess-ci
- spec:
-   replicas: 1
-   selector:
-     matchLabels:
-       app.kubernetes.io/instance: release-name-element-web
-   strategy:
-     type: RollingUpdate
-     rollingUpdate:
-       maxSurge: 2
-       maxUnavailable: 0
-   template:
-     metadata:
-       labels:
-         app.kubernetes.io/managed-by: Helm
-         app.kubernetes.io/part-of: matrix-stack
-         app.kubernetes.io/component: matrix-client
-         app.kubernetes.io/name: element-web
-         app.kubernetes.io/instance: release-name-element-web
-         app.kubernetes.io/version: "v1.12.10"
-         k8s.element.io/element-web-config-hash: "994c7bbea2846e7cd12de639bba56e30f0974be0"
-         k8s.element.io/nginx-config-hash: "ebf74e60cb1f5e455ca5140db2681c39df455fd6"
-       annotations:
-         checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-         checkov.io/skip2: CKV_K8S_43=No digests
-         checkov.io/skip3: CKV2_K8S_6=No network policy yet
-     spec:
-       automountServiceAccountToken: false
-       serviceAccountName: release-name-element-web
-       securityContext:
-         fsGroup: 10004
-         runAsGroup: 10004
-         runAsNonRoot: true
-         runAsUser: 10004
-         seccompProfile:
-           type: RuntimeDefault
-         supplementalGroups: []
-       restartPolicy: Always
-       topologySpreadConstraints:
-         - labelSelector:
-             matchLabels:
-               app.kubernetes.io/instance: release-name-element-web
-           matchLabelKeys:
-             - pod-template-hash
-           maxSkew: 1
-           topologyKey: kubernetes.io/hostname
-           whenUnsatisfiable: ScheduleAnyway
-       containers:
-         - name: element-web
-           image: "oci.element.io/element-web:v1.12.10"
-           imagePullPolicy: Always
-           env:
-             - name: NGINX_ENVSUBST_TEMPLATE_DIR
-               value: /non-existant-so-that-this-works-with-read-only-root-filesystem
-           securityContext:
-             allowPrivilegeEscalation: false
-             capabilities:
-               drop:
-                 - ALL
-             readOnlyRootFilesystem: true
-           ports:
-             - containerPort: 8080
-               name: element
-               protocol: TCP
-           livenessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: element
-               scheme: HTTP
-           readinessProbe:
-             failureThreshold: 3
-             periodSeconds: 3
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: element
-               scheme: HTTP
-           startupProbe:
-             failureThreshold: 4
-             periodSeconds: 3
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: element
-               scheme: HTTP
-           resources:
-             limits:
-               memory: 200Mi
-             requests:
-               cpu: 50m
-               memory: 50Mi
-           volumeMounts:
-             - mountPath: /app/config.json
-               name: config
-               readOnly: true
-               subPath: config.json
-             - mountPath: /etc/nginx/conf.d/default.conf
-               name: nginx-config
-               readOnly: true
-               subPath: default.conf
-             - mountPath: /etc/nginx/conf.d/http_customisations.conf
-               name: nginx-config
-               readOnly: true
-               subPath: http_customisations.conf
-             - mountPath: /etc/nginx/security_headers.conf
-               name: nginx-config
-               readOnly: true
-               subPath: security_headers.conf
-             - mountPath: /tmp
-               name: nginx-tmp
-       volumes:
-         - configMap:
-             defaultMode: 420
-             name: release-name-element-web
-           name: config
-         - configMap:
-             defaultMode: 420
-             name: release-name-element-web-nginx
-           name: nginx-config
-         - emptyDir:
-             medium: Memory
-           name: nginx-tmp


@@ Ingress/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/ingress.yaml
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- spec:
-   tls:
-     - hosts:
-         - "element.ess.localhost"
-   rules:
-     - host: "element.ess.localhost"
-       http:
-         paths:
-           - path: /
-             pathType: Prefix
-             backend:
-               service:
-                 name: release-name-element-web
-                 port:
-                   name: web


@@ Service/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/service.yaml
- apiVersion: v1
- kind: Service
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- spec:
-   type: ClusterIP
-   internalTrafficPolicy: Cluster
-   ipFamilyPolicy: PreferDualStack
-   ports:
-     - port: 80
-       targetPort: element
-       name: web
-   selector:
-     app.kubernetes.io/instance: release-name-element-web


@@ ServiceAccount/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/serviceaccount.yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- automountServiceAccountToken: false

element-web-minimal-values.yaml
@@ ConfigMap/ess-ci/release-name-element-web-nginx @@
- ---
- # Source: matrix-stack/templates/element-web/nginx_configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web-nginx
-   namespace: ess-ci
- data:
-   default.conf: |
-     # Copy of https://github.com/element-hq/element-web/blob/v1.11.97/docker/nginx-templates/default.conf.template but
-     # * not as a template, using a hard-coded port
-     # * the recommendations from https://github.com/element-hq/element-web/tree/v1.11.97?tab=readme-ov-file#configuration-best-practices added
-     # * /health added for k8s
-     # * setting a charset
-     # * ensuring our security headers include are applied, whether or not paths are in location blocks or not
-     server {
-       listen       8080;
-       listen  [::]:8080 ipv6only=on;
-       server_name  localhost;
- 
-       root   /usr/share/nginx/html;  # noqa
-       index  index.html;
-       charset utf-8;
- 
-       include /etc/nginx/security_headers.conf;
- 
-       # Set no-cache for the version, config and index.html
-       # so that browsers always check for a new copy of Element Web.
-       # NB http://your-domain/ and http://your-domain/? are also covered by this
- 
-       location = /index.html {
-           add_header Cache-Control "no-cache";
-           include /etc/nginx/security_headers.conf;
-       }
-       location = /version {
-           add_header Cache-Control "no-cache";
-           include /etc/nginx/security_headers.conf;
-       }
-       # covers config.json and config.hostname.json requests as it is prefix.
-       location /config {
-           # Serving /app/config.json as per https://github.com/element-hq/element-web/blob/v1.11.97/docker/docker-entrypoint.d/18-load-element-modules.sh#L15
-           root /tmp/element-web-config;
-           add_header Cache-Control "no-cache";
-           include /etc/nginx/security_headers.conf;
-       }
-       location /modules {
-           alias /modules;
-       }
-       location = /health {
-           allow all;
-           default_type 'application/json';
-           return 200 '{"status": "ok"}';
-       }
-       # redirect server error pages to the static page /50x.html
-       #
-       error_page   500 502 503 504  /50x.html;
-     }
-   # Customisations that we do at the http rather than the server level
-   http_customisations.conf: |
-     server_tokens off;
-     set_real_ip_from 0.0.0.0/0;
-     set_real_ip_from ::/0;
-     real_ip_header X-Forwarded-For;
-   # For repeated inclusion in default.conf because the add_header directives need to be repeated as per
-   # https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header as they are only inherited from
-   # the server block iff there's no add_header directives in the location block
-   security_headers.conf: |
-     # Copyright 2025 New Vector Ltd
-     # Copyright 2025 Element Creations Ltd
-     # SPDX-License-Identifier: AGPL-3.0-only
- 
-     add_header Content-Security-Policy "frame-ancestors 'self'";
-     add_header X-Content-Type-Options nosniff;
-     add_header X-Frame-Options SAMEORIGIN;
-     add_header X-Robots-Tag "noindex, nofollow, noarchive, noimageindex";
-     add_header X-XSS-Protection "1; mode=block";


@@ ConfigMap/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- data:
-   config.json: |
-     {
-       "bug_report_endpoint_url": "https://rageshakes.element.io/api/submit",
-       "default_server_config": {
-         "m.homeserver": {}
-       },
-       "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
-       "mobile_guide_app_variant": "element",
-       "setting_defaults": {}
-     }


@@ Deployment/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/deployment.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-     k8s.element.io/element-web-config-hash: "994c7bbea2846e7cd12de639bba56e30f0974be0"
-     k8s.element.io/nginx-config-hash: "ebf74e60cb1f5e455ca5140db2681c39df455fd6"
-   name: release-name-element-web
-   namespace: ess-ci
- spec:
-   replicas: 1
-   selector:
-     matchLabels:
-       app.kubernetes.io/instance: release-name-element-web
-   strategy:
-     type: RollingUpdate
-     rollingUpdate:
-       maxSurge: 2
-       maxUnavailable: 0
-   template:
-     metadata:
-       labels:
-         app.kubernetes.io/managed-by: Helm
-         app.kubernetes.io/part-of: matrix-stack
-         app.kubernetes.io/component: matrix-client
-         app.kubernetes.io/name: element-web
-         app.kubernetes.io/instance: release-name-element-web
-         app.kubernetes.io/version: "v1.12.10"
-         k8s.element.io/element-web-config-hash: "994c7bbea2846e7cd12de639bba56e30f0974be0"
-         k8s.element.io/nginx-config-hash: "ebf74e60cb1f5e455ca5140db2681c39df455fd6"
-     spec:
-       automountServiceAccountToken: false
-       serviceAccountName: release-name-element-web
-       securityContext:
-         fsGroup: 10004
-         runAsGroup: 10004
-         runAsNonRoot: true
-         runAsUser: 10004
-         seccompProfile:
-           type: RuntimeDefault
-         supplementalGroups: []
-       restartPolicy: Always
-       topologySpreadConstraints:
-         - labelSelector:
-             matchLabels:
-               app.kubernetes.io/instance: release-name-element-web
-           matchLabelKeys:
-             - pod-template-hash
-           maxSkew: 1
-           topologyKey: kubernetes.io/hostname
-           whenUnsatisfiable: ScheduleAnyway
-       containers:
-         - name: element-web
-           image: "oci.element.io/element-web:v1.12.10"
-           imagePullPolicy: Always
-           env:
-             - name: NGINX_ENVSUBST_TEMPLATE_DIR
-               value: /non-existant-so-that-this-works-with-read-only-root-filesystem
-           securityContext:
-             allowPrivilegeEscalation: false
-             capabilities:
-               drop:
-                 - ALL
-             readOnlyRootFilesystem: true
-           ports:
-             - containerPort: 8080
-               name: element
-               protocol: TCP
-           livenessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: element
-               scheme: HTTP
-           readinessProbe:
-             failureThreshold: 3
-             periodSeconds: 3
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: element
-               scheme: HTTP
-           startupProbe:
-             failureThreshold: 4
-             periodSeconds: 3
-             successThreshold: 1
-             timeoutSeconds: 1
-             httpGet:
-               path: /health
-               port: element
-               scheme: HTTP
-           resources:
-             limits:
-               memory: 200Mi
-             requests:
-               cpu: 50m
-               memory: 50Mi
-           volumeMounts:
-             - mountPath: /app/config.json
-               name: config
-               readOnly: true
-               subPath: config.json
-             - mountPath: /etc/nginx/conf.d/default.conf
-               name: nginx-config
-               readOnly: true
-               subPath: default.conf
-             - mountPath: /etc/nginx/conf.d/http_customisations.conf
-               name: nginx-config
-               readOnly: true
-               subPath: http_customisations.conf
-             - mountPath: /etc/nginx/security_headers.conf
-               name: nginx-config
-               readOnly: true
-               subPath: security_headers.conf
-             - mountPath: /tmp
-               name: nginx-tmp
-       volumes:
-         - configMap:
-             defaultMode: 420
-             name: release-name-element-web
-           name: config
-         - configMap:
-             defaultMode: 420
-             name: release-name-element-web-nginx
-           name: nginx-config
-         - emptyDir:
-             medium: Memory
-           name: nginx-tmp


@@ Ingress/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/ingress.yaml
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- spec:
-   tls:
-     - hosts:
-         - "element.ess.localhost"
-   rules:
-     - host: "element.ess.localhost"
-       http:
-         paths:
-           - path: /
-             pathType: Prefix
-             backend:
-               service:
-                 name: release-name-element-web
-                 port:
-                   name: web


@@ Service/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/service.yaml
- apiVersion: v1
- kind: Service
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- spec:
-   type: ClusterIP
-   internalTrafficPolicy: Cluster
-   ipFamilyPolicy: PreferDualStack
-   ports:
-     - port: 80
-       targetPort: element
-       name: web
-   selector:
-     app.kubernetes.io/instance: release-name-element-web


@@ ServiceAccount/ess-ci/release-name-element-web @@
- ---
- # Source: matrix-stack/templates/element-web/serviceaccount.yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-client
-     app.kubernetes.io/name: element-web
-     app.kubernetes.io/instance: release-name-element-web
-     app.kubernetes.io/version: "v1.12.10"
-   name: release-name-element-web
-   namespace: ess-ci
- automountServiceAccountToken: false

example-default-enabled-components-checkov-values.yaml
@@ Deployment/ess-ci/release-name-haproxy - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-authentication-service - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-rtc-authorisation-service - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-rtc-sfu - spec.template.spec @@
- restartPolicy: Always



@@ Ingress/ess-ci/release-name-synapse - metadata @@
+   test: true



@@ Job/ess-ci/release-name-deployment-markers-post - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ Job/ess-ci/release-name-deployment-markers-pre - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ Job/ess-ci/release-name-init-secrets - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ StatefulSet/ess-ci/release-name-postgres - spec.template.spec @@
- restartPolicy: Always



@@ StatefulSet/ess-ci/release-name-synapse-main - spec.template.spec @@
- restartPolicy: Always

example-default-enabled-components-values.yaml
@@ Deployment/ess-ci/release-name-haproxy - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-authentication-service - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-rtc-authorisation-service - spec.template.spec @@
- restartPolicy: Always



@@ Deployment/ess-ci/release-name-matrix-rtc-sfu - spec.template.spec @@
- restartPolicy: Always



@@ Ingress/ess-ci/release-name-synapse - metadata @@
+   test: true



@@ Job/ess-ci/release-name-deployment-markers-post - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ Job/ess-ci/release-name-deployment-markers-pre - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ Job/ess-ci/release-name-init-secrets - spec.template.spec.restartPolicy @@
- Never
+ OnFailure



@@ StatefulSet/ess-ci/release-name-postgres - spec.template.spec @@
- restartPolicy: Always



@@ StatefulSet/ess-ci/release-name-synapse-main - spec.template.spec @@
- restartPolicy: Always

hookshot-checkov-values.yaml
@@ ConfigMap/ess-ci/release-name-hookshot @@
- ---
- # Source: matrix-stack/templates/hookshot/hookshot_configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   name: release-name-hookshot
-   namespace: ess-ci
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-integrations
-     app.kubernetes.io/name: hookshot
-     app.kubernetes.io/instance: release-name-hookshot
-     app.kubernetes.io/version: "7.3.2"
- data:
-   config-underride.yaml: |
-     widgets:
-       roomSetupWidget:
-         addOnInvite: true
- 
-     permissions:
-     # Allow all users to send commands to existing services
-     - actor: "ess.localhost"
-       services:
-       - service: "*"
-         level: manageConnections
-   config-override.yaml: |
-     bridge:
-       domain: "ess.localhost"
-       port: 9993
-       bindAddress: 0.0.0.0
- 
-     passFile: /secrets/release-name-generated/HOOKSHOT_RSA_PASSKEY
- 
-     cache:
-       redisUri: "redis://release-name-redis.ess-ci.svc.cluster.local.:6379"
- 
-     logging:
-       level: info
- 
-     metrics:
-       enabled: true
- 
-     listeners:
-       - port: 7775
-         bindAddress: 0.0.0.0
-         resources:
-           - webhooks
-       - port: 7777
-         bindAddress: 0.0.0.0
-         resources:
-           - metrics
-       - port: 7778
-         bindAddress: 0.0.0.0
-         resources:
-           - widgets
- 
-     generic:
- 
-       urlPrefix: https://hookshot.ess.localhost/webhook
- 
- 
-     widgets:
-       publicUrl: https://hookshot.ess.localhost/widgetapi/v1/static


@@ ConfigMap/ess-ci/release-name-init-secrets @@
- ---
- # Source: matrix-stack/templates/init-secrets/configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   name: release-name-init-secrets
-   namespace: ess-ci
-   annotations:
-     "helm.sh/hook": pre-install,pre-upgrade
-     "helm.sh/hook-weight": "-11"
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-tools
-     app.kubernetes.io/name: init-secrets
-     app.kubernetes.io/instance: release-name-init-secrets
-     app.kubernetes.io/version: "0.7.3"
- data:
-   hookshot-registration.yaml: |
-     rate_limited: false
-     namespaces: {}
-     id: hookshot
-     as_token: "${AS_TOKEN}"
-     hs_token: "${HS_TOKEN}"
-     url: "http://release-name-hookshot.ess-ci.svc.cluster.local.:9993"
-     sender_localpart: hookshot
- 
-     org.matrix.msc3202: true


@@ ConfigMap/ess-ci/release-name-redis @@
- ---
- # Source: matrix-stack/templates/redis/redis_configmap.yaml
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-pubsub-small-cache
-     app.kubernetes.io/name: redis
-     app.kubernetes.io/instance: release-name-redis
-     app.kubernetes.io/version: "7.4-alpine"
-   name: release-name-redis
-   namespace: ess-ci
- data:
-   redis.conf: |
-     # Do not require a password
-     protected-mode no
-     port 6379
- 
-     tcp-backlog 511
-     tcp-keepalive 300
- 
-     # Never close the connection
-     timeout 0
- 
-     # We run the redis in a container so disable both of these
-     daemonize no
-     supervised no
- 
-     loglevel notice
-     logfile ''
- 
-     databases 16
-     always-show-logo no
-     stop-writes-on-bgsave-error yes
- 
-     # We never save to the disk
-     save ''
- 
-     replica-serve-stale-data yes
-     replica-read-only yes
-     repl-diskless-sync no
-     repl-diskless-sync-delay 5
-     repl-diskless-load disabled
-     repl-disable-tcp-nodelay no
-     replica-priority 100
-     acllog-max-len 128
- 
-     lazyfree-lazy-eviction no
-     lazyfree-lazy-expire no
-     lazyfree-lazy-server-del no
-     replica-lazy-flush no
- 
-     lazyfree-lazy-user-del no
- 
-     lazyfree-lazy-user-flush no
-     oom-score-adj no
-     oom-score-adj-values 0 200 800
- 
-     disable-thp yes
- 
-     appendonly no
-     appendfilename 'appendonly.aof'
-     appendfsync everysec
- 
-     no-appendfsync-on-rewrite no
- 
-     auto-aof-rewrite-percentage 100
-     auto-aof-rewrite-min-size 64mb
-     aof-load-truncated yes
-     aof-use-rdb-preamble yes
-     lua-time-limit 5000
-     slowlog-log-slower-than 10000
-     slowlog-max-len 128
-     latency-monitor-threshold 0
-     notify-keyspace-events ""
-     hash-max-ziplist-entries 512
-     hash-max-ziplist-value 64
-     list-max-ziplist-size -2
-     list-compress-depth 0
-     set-max-intset-entries 512
-     zset-max-ziplist-entries 128
-     zset-max-ziplist-value 64
-     hll-sparse-max-bytes 3000
-     stream-node-max-bytes 4096
-     stream-node-max-entries 100
-     activerehashing yes
-     client-output-buffer-limit normal 0 0 0
-     client-output-buffer-limit replica 256mb 64mb 60
-     client-output-buffer-limit pubsub 32mb 8mb 60
- 
-     # Hz is the freuqency at which background tasks are performed, we keep this low to save CPU
-     hz 1
- 
-     # The hz value is increased to scale with the number of clients connected.
-     dynamic-hz yes
- 
-     aof-rewrite-incremental-fsync yes
-     rdb-save-incremental-fsync yes
-     jemalloc-bg-thread yes
- 
-     maxmemory 40mb
-     maxmemory-policy allkeys-lru


@@ Deployment/ess-ci/release-name-redis @@
- ---
- # Source: matrix-stack/templates/redis/redis_deployment.yaml
- apiVersion: apps/v1
- kind: Deployment
- metadata:
-   annotations:
-     checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-     checkov.io/skip2: CKV_K8S_43=No digests
-     checkov.io/skip3: CKV2_K8S_6=No network policy yet
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-pubsub-small-cache
-     app.kubernetes.io/name: redis
-     app.kubernetes.io/instance: release-name-redis
-     app.kubernetes.io/version: "7.4-alpine"
-     k8s.element.io/redis-config-hash: "3034b3cfe78419348e36bb348fad98b46a736334"
-   name: release-name-redis
-   namespace: ess-ci
- spec:
-   replicas: 1
-   selector:
-     matchLabels:
-       app.kubernetes.io/instance: release-name-redis
-   strategy:
-     type: RollingUpdate
-     rollingUpdate:
-       maxSurge: 2
-       maxUnavailable: 0
-   template:
-     metadata:
-       labels:
-         app.kubernetes.io/managed-by: Helm
-         app.kubernetes.io/part-of: matrix-stack
-         app.kubernetes.io/component: matrix-pubsub-small-cache
-         app.kubernetes.io/name: redis
-         app.kubernetes.io/instance: release-name-redis
-         app.kubernetes.io/version: "7.4-alpine"
-         k8s.element.io/redis-config-hash: "3034b3cfe78419348e36bb348fad98b46a736334"
-       annotations:
-         checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-         checkov.io/skip2: CKV_K8S_43=No digests
-         checkov.io/skip3: CKV2_K8S_6=No network policy yet
-     spec:
-       automountServiceAccountToken: false
-       serviceAccountName: release-name-redis
-       securityContext:
-         fsGroup: 10002
-         runAsGroup: 10002
-         runAsNonRoot: true
-         runAsUser: 10002
-         seccompProfile:
-           type: RuntimeDefault
-         supplementalGroups: []
-       restartPolicy: Always
-       topologySpreadConstraints:
-         - labelSelector:
-             matchLabels:
-               app.kubernetes.io/instance: release-name-redis
-           matchLabelKeys:
-             - pod-template-hash
-           maxSkew: 1
-           topologyKey: kubernetes.io/hostname
-           whenUnsatisfiable: ScheduleAnyway
-       containers:
-         - name: redis
-           args:
-             - "/config/redis.conf"
-           image: "docker.io/library/redis:7.4-alpine"
-           imagePullPolicy: Always
-           securityContext:
-             allowPrivilegeEscalation: false
-             capabilities:
-               drop:
-                 - ALL
-             readOnlyRootFilesystem: true
-           ports:
-             - containerPort: 6379
-               name: redis
-               protocol: TCP
-           startupProbe:
-             failureThreshold: 5
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             tcpSocket:
-               port: redis
-           livenessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             tcpSocket:
-               port: redis
-           readinessProbe:
-             failureThreshold: 3
-             periodSeconds: 10
-             successThreshold: 1
-             timeoutSeconds: 1
-             exec:
-               command:
-                 - redis-cli
-                 - ping
-           resources:
-             limits:
-               memory: 50Mi
-             requests:
-               cpu: 50m
-               memory: 50Mi
-           volumeMounts:
-             - mountPath: /config/redis.conf
-               name: config
-               readOnly: true
-               subPath: redis.conf
-       volumes:
-         - configMap:
-             name: "release-name-redis"
-             defaultMode: 420
-           name: config


@@ Ingress/ess-ci/release-name-hookshot @@
- ---
- # Source: matrix-stack/templates/hookshot/hookshot_ingress.yaml
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-integrations
-     app.kubernetes.io/name: hookshot
-     app.kubernetes.io/instance: release-name-hookshot
-     app.kubernetes.io/version: "3.2-alpine"
-   name: release-name-hookshot
-   namespace: ess-ci
- spec:
-   tls:
-     - hosts:
-         - "hookshot.ess.localhost"
-   rules:
-     - host: "hookshot.ess.localhost"
-       http:
-         paths:
-           - path: /widgetapi/v1
-             pathType: Prefix
-             backend:
-               service:
-                 name: "release-name-hookshot"
-                 port:
-                   name: widgets
-           - path: /
-             pathType: Prefix
-             backend:
-               service:
-                 name: "release-name-hookshot"
-                 port:
-                   name: webhooks


@@ Job/ess-ci/release-name-init-secrets @@
- ---
- # Source: matrix-stack/templates/init-secrets/job.yaml
- apiVersion: batch/v1
- kind: Job
- metadata:
-   name: release-name-init-secrets
-   namespace: ess-ci
-   annotations:
-     "helm.sh/hook": pre-install,pre-upgrade
-     "helm.sh/hook-weight": "-10"
-     checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-     checkov.io/skip2: CKV_K8S_43=No digests
-     checkov.io/skip3: CKV2_K8S_6=No network policy yet
-     checkov.io/skip4: CKV_K8S_38=The job needs a service account
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-tools
-     app.kubernetes.io/name: init-secrets
-     app.kubernetes.io/instance: release-name-init-secrets
-     app.kubernetes.io/version: "0.7.3"
- spec:
-   backoffLimit: 6
-   completionMode: NonIndexed
-   completions: 1
-   manualSelector: false
-   parallelism: 1
-   podReplacementPolicy: TerminatingOrFailed
-   template:
-     metadata:
-       annotations:
-         checkov.io/skip1: CKV_K8S_11=We deliberately don't set CPU limits. Pod is BestEffort not Guaranteed
-         checkov.io/skip2: CKV_K8S_43=No digests
-         checkov.io/skip3: CKV2_K8S_6=No network policy yet
-         checkov.io/skip4: CKV_K8S_38=The job needs a service account
-       labels:
-         app.kubernetes.io/managed-by: Helm
-         app.kubernetes.io/part-of: matrix-stack
-         app.kubernetes.io/component: matrix-tools
-         app.kubernetes.io/name: init-secrets
-         app.kubernetes.io/instance: release-name-init-secrets
-         app.kubernetes.io/version: "0.7.3"
-     spec:
-       automountServiceAccountToken: true
-       serviceAccountName: release-name-init-secrets
-       securityContext:
-         fsGroup: 10010
-         runAsGroup: 10010
-         runAsNonRoot: true
-         runAsUser: 10010
-         seccompProfile:
-           type: RuntimeDefault
-         supplementalGroups: []
-       restartPolicy: Never
-       topologySpreadConstraints:
-         - labelSelector:
-             matchLabels:
-               app.kubernetes.io/instance: release-name-init-secrets
-           matchLabelKeys: []
-           maxSkew: 1
-           topologyKey: kubernetes.io/hostname
-           whenUnsatisfiable: ScheduleAnyway
-       containers:
-         - name: init-secrets
-           image: "ghcr.io/element-hq/ess-helm/matrix-tools:0.7.3"
-           imagePullPolicy: Always
-           securityContext:
-             allowPrivilegeEscalation: false
-             capabilities:
-               drop:
-                 - ALL
-             readOnlyRootFilesystem: true
-           resources:
-             limits:
-               memory: 200Mi
-             requests:
-               cpu: 50m
-               memory: 50Mi
-           env:
-             - name: NAMESPACE
-               value: ess-ci
-           args:
-             - "generate-secrets"
-             - "-secrets"
-             - "release-name-generated:HOOKSHOT_REGISTRATION:registration:/registration-templates/hookshot-registration.yaml,release-name-generated:HOOKSHOT_RSA_PASSKEY:rsa:4096:pem"
-             - "-labels"
-             - "app.kubernetes.io/managed-by=Helm,app.kubernetes.io/part-of=matrix-stack,app.kubernetes.io/component=matrix-tools,app.kubernetes.io/name=init-secrets,app.kubernetes.io/instance=release-name-init-secrets,app.kubernetes.io/version=0.7.3"
-           volumeMounts:
-             - mountPath: "/registration-templates"
-               name: registration-templates
-               readOnly: true
-       volumes:
-         - configMap:
-             defaultMode: 420
-             name: release-name-init-secrets
-           name: registration-templates


@@ Role/ess-ci/release-name-init-secrets @@
- ---
- # Source: matrix-stack/templates/init-secrets/role.yaml
- apiVersion: rbac.authorization.k8s.io/v1
- kind: Role
- metadata:
-   name: release-name-init-secrets
-   namespace: ess-ci
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-tools
-     app.kubernetes.io/name: init-secrets
-     app.kubernetes.io/instance: release-name-init-secrets
-     app.kubernetes.io/version: "0.7.3"
-   annotations:
-     "helm.sh/hook": pre-install,pre-upgrade
-     "helm.sh/hook-weight": "-11"
- rules:
-   - apiGroups: [""]
-     resources: ["secrets"]
-     verbs: ["create"]
-   - apiGroups: [""]
-     resources: ["secrets"]
-     resourceNames: ["release-name-generated"]
-     verbs: ["get", "update"]


@@ RoleBinding/ess-ci/release-name-init-secrets @@
- ---
- # Source: matrix-stack/templates/init-secrets/rolebinding.yaml
- apiVersion: rbac.authorization.k8s.io/v1
- kind: RoleBinding
- metadata:
-   name: release-name-init-secrets
-   namespace: ess-ci
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-tools
-     app.kubernetes.io/name: init-secrets
-     app.kubernetes.io/instance: release-name-init-secrets
-     app.kubernetes.io/version: "0.7.3"
-   annotations:
-     "helm.sh/hook": pre-install,pre-upgrade
-     "helm.sh/hook-weight": "-11"
- roleRef:
-   apiGroup: rbac.authorization.k8s.io
-   kind: Role
-   name: release-name-init-secrets
- subjects:
-   - kind: ServiceAccount
-     name: release-name-init-secrets
-     namespace: ess-ci


@@ Secret/ess-ci/release-name-hookshot @@
- ---
- # Source: matrix-stack/templates/hookshot/hookshot_secret.yaml
- apiVersion: v1
- kind: Secret
- metadata:
-   name: release-name-hookshot
-   namespace: ess-ci
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-integrations
-     app.kubernetes.io/name: hookshot
-     app.kubernetes.io/instance: release-name-hookshot
-     app.kubernetes.io/version: "7.3.2"
- type: Opaque
- data:


@@ Service/ess-ci/release-name-hookshot @@
- ---
- # Source: matrix-stack/templates/hookshot/hookshot_service.yaml
- apiVersion: v1
- kind: Service
- metadata:
-   name: release-name-hookshot
-   namespace: ess-ci
-   labels:
-     helm.sh/chart: "matrix-stack-26.2.3-dev"
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/part-of: matrix-stack
-     app.kubernetes.io/component: matrix-integrations
-     app.kub...*[Comment body truncated]*

@JoshuaHassler
Copy link
Copy Markdown
Author

JoshuaHassler commented Feb 20, 2026

Hey @benbz, no problem, you guys seem quite busy. I don't use GitHub for work, so I didn't know whether it would auto-add a reviewer.

To shed some initial light on your questions:

Is there a reason to put the Gateway values in with the Ingress values?

  • i.e. have synapse.gateway.host as well as synapse.ingress.host use the present/absence of ...host to determine which to use?
  • Or pull synapse.ingress.host up to synapse.host and have your type value or enabled flags under synapse {gateway,ingress}

For the actual Gateway config, I think that it should be promoted from ingress.gateway to a top-level key (gateway). For most people using this API, they will probably only want a single gateway, so they aren't paying for a bunch of cloud LoadBalancers needlessly. Multi-gateway configs can be handled externally through the parentRefs as an "advanced" feature.

In terms of overall structure, I am not 100% sure what the best approach is, but this was my thought process.

  • We have a set of config options that are shared between both ingress and httproute resources. To handle this we can either...
    • place shared keys in the parent component
      • This is clean for the "host" key, but would lead to things like "ingressAnnotations", etc
      • We would need an ingress-esq key top-level key anyway to group the global versions of these shared options
      • As a benifit we don't need to change the associated shared templates if other ingress types are added.
    • duplicate shared keys
      • Don't try to share keys between HTTPRoute and Ingress resources
      • Leads to more complicated templating that needs to be modified for each ingress type (some shared "core" template, then a wrapper to shim in the values from that specific config)
      • Potential code drift for shared keys
      • arguably a simpler config experience for the end user
      • works well with the existing global + local override convention
    • shared state + specific (this implementation)
      • works well with the existing global + local override convention
      • able to reuse the existing templates mostly
      • no drift on shared keys
      • IMO easier to add new ingress types in the future since you only have to create the helpers for that specific type.
      • I don't know how other people feel about specifying the "type" key

I don't know how much of this code base you share with Pro, but IMO the main benefit of the last approach is that it tends to require less refactoring when you add a new ingress type. For instance, if you have a customer request for Istio VirtualService or ambassador based ingresses, you don't need to modify the shared templates as much in the future. It would be mostly contained to updating ess-community.ess-library.ingress.isEnabled.

When the chart creates its own Gateway are providing wildcard certs via a single value still possible?

I think they are due to the element-io.gateway.tlsConfig helper still using coalesce but want to confirm
Alternatively would your expectation be that if you need that setup you must provide an existing gateway?

It should be. I currently use a wildcard certificate issued by Let's Encrypt. You just specify the secret name as the ingress.tlsSecret value.

If you are using cert manager, the built-in gateway config will cause it to issue a cert with each hostname specified. This was so it could be used without setting up a DNS-01 solver. I don't have strong opinions on this. The gateway setup is a bit more complicated than it needs to be to make it work with a standard HTTP solver. It can be simplified once cert-manger supports specifying the certificate on the HTTPRoute resource (current WIP it looks like).

Are there other charts you've been looking at for inspiration on how to structure the values / what functionality to provide?

Managing Kubernetes deployments is part of my day job, so I have seen far too many charts lol. From my perspective, there is no industry consensus on how to expose this config. For me, the easier to work with charts tend to handle it similarly to this structure. For example Harbor and the Gerrit Operator.

For the functionality, basically all charts I work with that provide a gateway/http route based ingress provide the following features that I am specifically looking to add:

  1. Some way to completely disable ingress as an "escape hatch."
  2. generation of Ingress resources
  3. generation of HTTPRoutes
  4. (optional) generation of gateways.
    • In practice, a lot of production environments disable this and just use their own existing gateways, but I think this is particularly valuable to the community deployments, where they are probably only running ess-helm in the cluster.

As a last though I have structured it to be an exclusive "ingress or httproute" for each component. This is not actually a requirement, and I can change it so that both are possible. I just think having it exclusive makes a user less likely to shoot themselves in the foot by accident.

I'll look into getting the tests updated and passing this weekend.

Thanks again!

@benbz
Copy link
Copy Markdown
Member

benbz commented Feb 23, 2026

Hey @benbz, no problem, you guys seem quite busy. I don't use GitHub for work, so I didn't know whether it would auto-add a reviewer.

To shed some initial light on your questions:

Is there a reason to put the Gateway values in with the Ingress values?

  • i.e. have synapse.gateway.host as well as synapse.ingress.host use the present/absence of ...host to determine which to use?
  • Or pull synapse.ingress.host up to synapse.host and have your type value or enabled flags under synapse {gateway,ingress}

For the actual Gateway config, I think that it should be promoted from ingress.gateway to a top-level key (gateway). For most people using this API, they will probably only want a single gateway, so they aren't paying for a bunch of cloud LoadBalancers needlessly. Multi-gateway configs can be handled externally through the parentRefs as an "advanced" feature.

Yup fully agree, we don't want to lead people to create lots of Gateway resources. I don't think either of the structures proposed above would do that though, they're more focused on whether an Ingress or HTTPRoute is to be constructed for a given component.

Whatever is chosen I think the chart should lead users towards constructing a single Gateway for the whole chart or specifying an existing Gateway for the whole chart (with per-component overrides to specify other existing Gateways). If users want per-component Gateways I think they should construct them out of band and specify them on a per-component basis.

So a top-level gateway key as a sibling to the top-level ingress key which specifies either construction of the chart's single Gateway or the default external Gateway to use makes sense to me. However this is modulo the discussion below about how things should be constructed on a per-component basis as I'd want the top-level/global config structure to mirror the per-component one plus or minus config options that do/don't make sense in a global context

In terms of overall structure, I am not 100% sure what the best approach is, but this was my thought process.

* We have a set of config options that are shared between both ingress and httproute resources. To handle this we can either...
  
  * place shared keys in the parent component
    
    * This is clean for the "host" key, but would lead to things like "ingressAnnotations", etc
    * We would need an ingress-esq key top-level key anyway to group the global versions of these shared options
    * As a benifit we don't need to change the associated shared templates if other ingress types are added.
  * duplicate shared keys
    
    * Don't try to share keys between HTTPRoute and Ingress resources
    * Leads to more complicated templating that needs to be modified for each ingress type (some shared "core" template, then a wrapper to shim in the values from that specific config)
    * Potential code drift for shared keys
    * arguably a simpler config experience for the end user
    * works well with the existing global + local override convention
  * shared state + specific (this implementation)
    
    * works well with the existing global + local override convention
    * able to reuse the existing templates mostly
    * no drift on shared keys
    * IMO easier to add new ingress types in the future since you only have to create the helpers for that specific type.
    * I don't know how other people feel about specifying the "type" key

My main problems with the current approach are twofold

  • Having a HTTPRoute subkey under ingress feels very confusing
  • Lack of backwards compatibility for className and controllerType
    • This could be worked around and eventually removed but <component>.ingress.Ingress.{className,controllerType} also feels confusing

Potentially the original sin here is having <component>.ingress rather than <component>.expose.ingress like the Harbor chart. Refactoring the chart, maintaining backwards compatibility, to make this change does not feel palatable.

My gut feeling is that keeping <component>.ingress and introducing <component>.gateway, <component>.gatewayApi or <component>.httpRoute is best. We accept the duplication of properties and rely on the schema and tests to ensure common behaviour around e.g. TLS Secret handling or annotation merging. Whatever the mechanism for detecting one or the other is active (be it presence/absence of <component>.ingress.host, <component>.ingress.enabled: true or .inboundTrafficMechanism: ingress`) we add validation that only 1 inbound traffic mechanism is active or not. i.e. we treat them as entirely independent things that just can't happen to be turned on at the same time.

@gaelgatelement would appreciate your thoughts on this

I don't know how much of this code base you share with Pro, but IMO the main benefit of the last approach is that it tends to require less refactoring when you add a new ingress type. For instance, if you have a customer request for Istio VirtualService or ambassador based ingresses, you don't need to modify the shared templates as much in the future. It would be mostly contained to updating ess-community.ess-library.ingress.isEnabled.

ESS Community is a strict subset of ESS Pro, so all the code here gets incorporated into ESS Pro, with extra options as required. We make heavy use of the tests to ensure the same behaviour carries over to additional components that are only in ESS Pro and have further tests to validate additional behaviour that's only in ESS Pro.

When the chart creates its own Gateway are providing wildcard certs via a single value still possible?

I think they are due to the element-io.gateway.tlsConfig helper still using coalesce but want to confirm
Alternatively would your expectation be that if you need that setup you must provide an existing gateway?

It should be. I currently use a wildcard certificate issued by Let's Encrypt. You just specify the secret name as the ingress.tlsSecret value.

If you are using cert manager, the built-in gateway config will cause it to issue a cert with each hostname specified. This was so it could be used without setting up a DNS-01 solver. I don't have strong opinions on this. The gateway setup is a bit more complicated than it needs to be to make it work with a standard HTTP solver. It can be simplified once cert-manger supports specifying the certificate on the HTTPRoute resource (current WIP it looks like).

👍

Are there other charts you've been looking at for inspiration on how to structure the values / what functionality to provide?

Managing Kubernetes deployments is part of my day job, so I have seen far too many charts lol. From my perspective, there is no industry consensus on how to expose this config. For me, the easier to work with charts tend to handle it similarly to this structure. For example Harbor and the Gerrit Operator.

Looking at Harbor, everything is under an expose key with type and ingress/route/etc subkeys, which most closely maps to option 2 (duplicate shared keys). The Gerrit example I don't think is particularly relevant as it is just passing ingress.type as an env var to the operator and there's no further customisation.

For the functionality, basically all charts I work with that provide a gateway/http route based ingress provide the following features that I am specifically looking to add:

1. Some way to completely disable ingress as an "escape hatch."

2. generation of Ingress resources

3. generation of HTTPRoutes

4. (optional) generation of gateways.
   
   * In practice, a lot of production environments disable this and just use their own existing gateways, but I think this is particularly valuable to the community deployments, where they are probably only running ess-helm in the cluster.

As a last though I have structured it to be an exclusive "ingress or httproute" for each component. This is not actually a requirement, and I can change it so that both are possible. I just think having it exclusive makes a user less likely to shoot themselves in the foot by accident.

I would be strongly in favour of exclusive Ingress or HTTPRoute too (in the xor sense that exactly 1 of them must be configured, configuring neither of them is not allowed)

@JoshuaHassler
Copy link
Copy Markdown
Author

JoshuaHassler commented Feb 26, 2026

Sorry for the delay in responding. I should have an updated patch tonight or tomorrow. It sounds like I am overall on track.

I've been putting some more thought into the config structure. Having the duplicate keys, I don’t think it will be as much of an issue as I had originally thought. I may be able to leverage the value schemas to simplify things, but I need to play around with them a bit to see how much of the JSON Schema spec they support. I also realized that HTTPRoute is probably a bad name, since certain components will likely use TCPRoutes in the future as an ingress option. For instance, SFU as an alternative to the NodePort model.

With that in mind, what do you think of the following structure?

  • .gateway: Global gateway config specifically for the Gateway resource.
  • .routes / .<component>.routes: configuration for the HTTP/TCP Routes. This is a bit more of a generic name so that each component can expose whatever route config it needs to function. For now, this is just HTTPRoutes.
  • .ingress / .<component>.ingress: left as it is now

I am not particularly tied to routes as the key name, but I think it is important to distinguish the gateway config from the route config. The gateway-specific options would only be “global only,” whereas the routes would follow the standard “global with component override” pattern.

For the selection of which ingress type to use, my initial thoughts would be to use a .inboundTrafficMechanism / .<component>.inboundTrafficMechanism with the options of ingress | gatewayApi | none for a few reasons. First, I feel like this makes it clear to a user that this is a mutually exclusive decision. Second, it has fewer edge cases to check than making sure only one “hosts” or “enabled” is set. Third, it is also easily enforceable with the values schema. Lastly, if someone is experimenting with multiple ingresses, they can set the values for both and easily switch between them.

While keeping the xor approach, I would also prefer to maintain the none option. I can understand if you would rather have me open another PR with it, but being able to disable ingress while keeping the component enabled gives much more flexibility in how things are deployed, particularly in more complex cluster setups that may not use ingress/gateways at all.

@JoshuaHassler
Copy link
Copy Markdown
Author

JoshuaHassler commented Feb 26, 2026

Turn out helm only supports a very limited subset of the JSON schema spec. This precludes any of the "fun" I was trying to have creating more composable ingress fragments. There are a few things that can probably be factored out into common fragments (like the global "service" object), but buy and large I just end up with a duplicate code path for the generation of the schema and values file.

I am still working on updating the helpers and templates, but I pushed most of the changes needed for the values file/schema. I also need to update the z_validation helpers to check that host is set on the appropriate resource instead of just on ingress.

Also an extremely minor bug I noticed while updating this. The ingress block macro only uses global to determine if the the host field should be generated. This results in it generating the host comment/key for the wellKnownDelegate block when it shouldn't.

I should have the updated templates and the start of testing pushed tomorrow after I finish cleaning it up.

"tlsSecret": {
"type": "string"
},
"service": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels we need a service_global.json rather than having this distinct between ingress_global.json and route_global.json

Comment on lines 16 to +17
{{- sub_schema_values.ingress() -}}
{{- sub_schema_values.routes() -}}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to merge these so that we have somewhere sensible to put a commented out inboundTrafficHandler for each component and that it ensures that the values for one or the other can't be forgotten

@benbz
Copy link
Copy Markdown
Member

benbz commented Feb 27, 2026

With that in mind, what do you think of the following structure?

* `.gateway`: Global gateway config specifically for the Gateway resource.
* `.routes / .<component>.routes`: configuration for the HTTP/TCP Routes. This is a bit more of a generic name so that each component can expose whatever route config it needs to function. For now, this is just HTTPRoutes.
* `.ingress / .<component>.ingress`: left as it is now

I don't object to this. The only improvement I can see would be to use gatewayApi for both the global gateway config and per-component routes.

Hmm. Looking through the diff so far has gateway for the global Gateway resource and a global routes for defaults all HTTPRoutes. So potentially what you have is better

I also realized that HTTPRoute is probably a bad name, since certain components will likely use TCPRoutes in the future as an ingress option. For instance, SFU as an alternative to the NodePort model.

Unsure whether we'd do this under *.routes/*.gatewayApi or the existing *.exposedServices set of configuration. For a further phase

For the selection of which ingress type to use, my initial thoughts would be to use a .inboundTrafficMechanism / .<component>.inboundTrafficMechanism with the options of ingress | gatewayApi | none for a few reasons. First, I feel like this makes it clear to a user that this is a mutually exclusive decision. Second, it has fewer edge cases to check than making sure only one “hosts” or “enabled” is set. Third, it is also easily enforceable with the values schema. Lastly, if someone is experimenting with multiple ingresses, they can set the values for both and easily switch between them.

While keeping the xor approach, I would also prefer to maintain the none option. I can understand if you would rather have me open another PR with it, but being able to disable ingress while keeping the component enabled gives much more flexibility in how things are deployed, particularly in more complex cluster setups that may not use ingress/gateways at all.

Makes sense. I can accept none as long as it is covered by manifest tests

I am still working on updating the helpers and templates, but I pushed most of the changes needed for the values file/schema.

Makes sense. I've only reviewed the values/schema changes as a result.

Thanks

@halkeye
Copy link
Copy Markdown

halkeye commented Mar 19, 2026

As synapse is one of my last nginx ingress, and I just found out about this chart to handle everything else as well, i'm very invested in this PR.

Is there anything I can do to help? my matrix isn't critical so I can help QA or whatever.

JoshuaHassler and others added 5 commits March 19, 2026 23:23
Add support for kubernetes Gateway/HTTPRoute resources as an alternative
ingress.
- Add conditional logic for HTTPRoute/Ingress generation
- Add HTTPRoute configuration to values files
- Refactor ingress values to seperate common keys
- Add option to disable ingress all together
- Add optional Gateway resource
@JoshuaHassler
Copy link
Copy Markdown
Author

I believe I have updated all the templates for the new system and done the necessary helper refactoring. At this point I think I just need to add the testing, which has been a bit of a challenge since the public docs on how to run the tests are incomplete/wrong.

I have been a bit time constrained over the last few weeks with work, but will have time to hopefully complete the testing updates this weekend. I rebased my branch onto their latest changes in case you want to test anything. I do use my branch for my own matrix server, but I am always happy to have more eyes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants