[feat]: Implement Traefik ingress support in Plane-EE Helm chart#198
[feat]: Implement Traefik ingress support in Plane-EE Helm chart#198pratapalakshmi wants to merge 3 commits intomasterfrom
Conversation
- Updated values.yaml to switch ingressClass from 'nginx' to 'traefik-external'. - Added new ingress-traefik.yaml template for Traefik IngressRoute configuration. - Introduced traefik-middleware.yaml for request body size limiting middleware. - Modified existing ingress.yaml to conditionally render based on the selected ingressClass.
WalkthroughAdds conditional Traefik support to the Plane Enterprise Helm chart: new Traefik IngressRoute, Middleware, and optional RBAC for secret reading, plus tightened nginx ingress rendering and a minor values.yaml comment. Resources are emitted only when ingress is enabled and ingress.ingressClass matches the target class. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…aefik' in Plane-EE Helm chart - Modified values.yaml to set ingressClass to 'nginx' and commented out 'traefik'. - Updated ingress-traefik.yaml and traefik-middleware.yaml templates to reflect the new ingressClass condition.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@charts/plane-enterprise/templates/ingress-traefik.yaml`:
- Around line 72-79: The body-limit middleware ({{ .Release.Name }}-body-limit)
is only attached to the catch-all rule (Host(`{{ .Values.license.licenseDomain
}}`) && PathPrefix(`/`)) so API/upload routes like /api/ and /auth/ remain
uncapped; update the Traefik ingress rules to include the same middleware on the
API/auth/upload rules that point to the {{ .Release.Name }}-web service (or
apply it globally to all host rules) so the 5 MB limit is enforced consistently,
and if you intentionally want selective coverage, add a short comment next to
the rule(s) explaining that rationale.
- Around line 81-82: The Traefik Ingress template currently hardcodes
tls.secretName ({{ default (printf "%s-ssl-cert" .Release.Name)
.Values.ssl.tls_secret_name }}) and therefore doesn’t support cert-manager
auto-provisioning; add an optional, conditional cert-manager Certificate
manifest (templated alongside the IngressRoute) that is rendered when
.Values.ssl.generateCerts (and optionally .Values.ssl.createIssuer) is true,
ensure it uses the same secretName and issuerRef as the existing nginx flow, and
wire any new values (e.g., ssl.certificateCommonName, ssl.dnsNames,
ssl.issuerName, ssl.issuerKind) into the template so users can enable automated
cert creation/renewal without manual steps.
- Around line 1-84: Add Traefik IngressRoute rules for the MinIO console and
RabbitMQ management UI when those services are locally deployed: insert two
additional route blocks (similar to existing ones) before the tls: block—one
matching Host(`{{ .Values.env.minioHost }}`) that forwards to service name {{
.Release.Name }}-minio on port 9090 when .Values.services.minio.local_setup is
true, and one matching Host(`{{ .Values.env.rabbitmqHost }}`) that forwards to
service name {{ .Release.Name }}-rabbitmq on port 15672 when
.Values.services.rabbitmq.local_setup is true; also update the tls section to
include those extra host entries (or add separate tls entries) so the
certificates cover minioHost and rabbitmqHost.
In `@charts/plane-enterprise/templates/traefik-middleware.yaml`:
- Line 1: The middleware template condition is too permissive compared to the
IngressRoute: update the conditional in templates/traefik-middleware.yaml to
require the same three checks used by the IngressRoute (ensure
.Values.ingress.enabled is true, .Values.ingress.ingressClass equals
"traefik-external", and .Values.license.licenseDomain is non-empty) so the
middleware is only rendered when an IngressRoute referencing it will also be
created; mirror the exact predicate logic found in ingress-traefik.yaml to keep
behavior consistent.
In `@charts/plane-enterprise/values.yaml`:
- Around line 28-30: The change to set ingressClass: 'traefik-external' in
values.yaml is a breaking default change for existing users; revert the default
back to 'nginx' (or leave it commented as before) so upgrades don't silently
switch Ingress controllers, or if you intend Traefik to be the new default, bump
the chart major version and add clear upgrade/migration notes explaining the
switch and required CRDs/controllers; refer to the ingressClass key and
ingress_annotations in values.yaml when making this change and update Chart.yaml
and CHANGELOG/UPGRADE notes accordingly.
- Line 29: The default ingress_annotations map currently contains an
nginx-specific key ("nginx.ingress.kubernetes.io/proxy-body-size": "5m") which
is ineffective when the default ingress class is Traefik; update the values.yaml
entry for ingress_annotations (the ingress_annotations map) to not include
nginx-only annotations by default — either set ingress_annotations to an empty
map or make the default conditional on the ingressClass (e.g., only populate the
nginx annotation when ingressClass == "nginx" or leave it empty so users
choosing nginx can supply their own overrides).
| {{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik-external") .Values.license.licenseDomain }} | ||
|
|
||
| apiVersion: traefik.io/v1alpha1 | ||
| kind: IngressRoute | ||
| metadata: | ||
| name: {{ .Release.Name }}-ingress | ||
| namespace: {{ .Release.Namespace }} | ||
| spec: | ||
| entryPoints: | ||
| - websecure | ||
|
|
||
| routes: | ||
|
|
||
| # IMPORTANT: specific paths FIRST | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/spaces/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-space | ||
| port: 3000 | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/god-mode/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-admin | ||
| port: 3000 | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/api/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-api | ||
| port: 8000 | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/auth/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-api | ||
| port: 8000 | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/graphql/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-api | ||
| port: 8000 | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/marketplace/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-api | ||
| port: 8000 | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/live/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-live | ||
| port: 3000 | ||
|
|
||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/silo/`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-silo | ||
| port: 3000 | ||
|
|
||
| {{- if and .Values.services.minio.local_setup .Values.env.docstore_bucket }} | ||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/{{ .Values.env.docstore_bucket }}`) | ||
| kind: Rule | ||
| services: | ||
| - name: {{ .Release.Name }}-minio | ||
| port: 9000 | ||
| {{- end }} | ||
|
|
||
| # LAST: catch all | ||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/`) | ||
| kind: Rule | ||
| middlewares: | ||
| - name: {{ .Release.Name }}-body-limit | ||
| services: | ||
| - name: {{ .Release.Name }}-web | ||
| port: 3000 | ||
|
|
||
| tls: | ||
| secretName: {{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }} | ||
|
|
||
| {{- end }} No newline at end of file |
There was a problem hiding this comment.
Missing MinIO console and RabbitMQ management virtual-host routes.
The nginx ingress.yaml defines separate host rules for minioHost (port 9090) and rabbitmqHost (port 15672) when those services are locally deployed. This Traefik IngressRoute omits both, so users who rely on the MinIO console or RabbitMQ management UI will lose access after switching to traefik-external.
Sketch of the missing routes (add before the closing `tls:` block)
{{- end }}
+ {{- if and .Values.services.minio.local_setup .Values.ingress.minioHost }}
+ - match: Host(`{{ .Values.ingress.minioHost }}`)
+ kind: Rule
+ services:
+ - name: {{ .Release.Name }}-minio
+ port: 9090
+ {{- end }}
+
+ {{- if and .Values.services.rabbitmq.local_setup .Values.ingress.rabbitmqHost }}
+ - match: Host(`{{ .Values.ingress.rabbitmqHost }}`)
+ kind: Rule
+ services:
+ - name: {{ .Release.Name }}-rabbitmq
+ port: 15672
+ {{- end }}
+
# LAST: catch allYou will also need to add the extra hosts to the TLS section if using a shared cert, or add separate tls entries.
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 1-1: syntax error: expected the node content, but found '-'
(syntax)
🤖 Prompt for AI Agents
In `@charts/plane-enterprise/templates/ingress-traefik.yaml` around lines 1 - 84,
Add Traefik IngressRoute rules for the MinIO console and RabbitMQ management UI
when those services are locally deployed: insert two additional route blocks
(similar to existing ones) before the tls: block—one matching Host(`{{
.Values.env.minioHost }}`) that forwards to service name {{ .Release.Name
}}-minio on port 9090 when .Values.services.minio.local_setup is true, and one
matching Host(`{{ .Values.env.rabbitmqHost }}`) that forwards to service name {{
.Release.Name }}-rabbitmq on port 15672 when
.Values.services.rabbitmq.local_setup is true; also update the tls section to
include those extra host entries (or add separate tls entries) so the
certificates cover minioHost and rabbitmqHost.
| # LAST: catch all | ||
| - match: Host(`{{ .Values.license.licenseDomain }}`) && PathPrefix(`/`) | ||
| kind: Rule | ||
| middlewares: | ||
| - name: {{ .Release.Name }}-body-limit | ||
| services: | ||
| - name: {{ .Release.Name }}-web | ||
| port: 3000 |
There was a problem hiding this comment.
Body-limit middleware applied only to the catch-all, not to API/upload routes.
In the nginx Ingress the proxy-body-size: 5m annotation applies globally to all paths. Here the body-limit middleware is only on the / catch-all, leaving /api/, /auth/, etc. without a request-body cap. If the intent is to enforce a 5 MB upload limit consistently, the middleware should be attached to all routes (or at least the API route that handles uploads).
If selective application is intentional, a brief comment explaining the rationale would help future maintainers.
🤖 Prompt for AI Agents
In `@charts/plane-enterprise/templates/ingress-traefik.yaml` around lines 72 - 79,
The body-limit middleware ({{ .Release.Name }}-body-limit) is only attached to
the catch-all rule (Host(`{{ .Values.license.licenseDomain }}`) &&
PathPrefix(`/`)) so API/upload routes like /api/ and /auth/ remain uncapped;
update the Traefik ingress rules to include the same middleware on the
API/auth/upload rules that point to the {{ .Release.Name }}-web service (or
apply it globally to all host rules) so the 5 MB limit is enforced consistently,
and if you intentionally want selective coverage, add a short comment next to
the rule(s) explaining that rationale.
| @@ -0,0 +1,11 @@ | |||
| {{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik-external") }} | |||
There was a problem hiding this comment.
Condition is less restrictive than the IngressRoute template.
The IngressRoute in ingress-traefik.yaml additionally checks .Values.license.licenseDomain, but this middleware does not. When licenseDomain is empty, the middleware will be created with no IngressRoute to reference it. Align the conditions for consistency.
Proposed fix
-{{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik-external") }}
+{{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik-external") .Values.license.licenseDomain }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik-external") }} | |
| {{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik-external") .Values.license.licenseDomain }} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 1-1: syntax error: expected the node content, but found '-'
(syntax)
🤖 Prompt for AI Agents
In `@charts/plane-enterprise/templates/traefik-middleware.yaml` at line 1, The
middleware template condition is too permissive compared to the IngressRoute:
update the conditional in templates/traefik-middleware.yaml to require the same
three checks used by the IngressRoute (ensure .Values.ingress.enabled is true,
.Values.ingress.ingressClass equals "traefik-external", and
.Values.license.licenseDomain is non-empty) so the middleware is only rendered
when an IngressRoute referencing it will also be created; mirror the exact
predicate logic found in ingress-traefik.yaml to keep behavior consistent.
| rabbitmqHost: '' | ||
| ingressClass: 'nginx' | ||
| # ingressClass: 'nginx' | ||
| ingress_annotations: { "nginx.ingress.kubernetes.io/proxy-body-size": "5m" } |
There was a problem hiding this comment.
Default ingress_annotations are nginx-specific but the default class is now Traefik.
"nginx.ingress.kubernetes.io/proxy-body-size": "5m" has no effect on a Traefik IngressRoute. With traefik-external as the default, this annotation is dead config and may confuse users. Consider either clearing the default annotations or making them conditional on the ingress class.
Possible fix
- ingress_annotations: { "nginx.ingress.kubernetes.io/proxy-body-size": "5m" }
+ ingress_annotations: {}Users who select nginx can supply annotations via their own values override.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ingress_annotations: { "nginx.ingress.kubernetes.io/proxy-body-size": "5m" } | |
| ingress_annotations: {} |
🤖 Prompt for AI Agents
In `@charts/plane-enterprise/values.yaml` at line 29, The default
ingress_annotations map currently contains an nginx-specific key
("nginx.ingress.kubernetes.io/proxy-body-size": "5m") which is ineffective when
the default ingress class is Traefik; update the values.yaml entry for
ingress_annotations (the ingress_annotations map) to not include nginx-only
annotations by default — either set ingress_annotations to an empty map or make
the default conditional on the ingressClass (e.g., only populate the nginx
annotation when ingressClass == "nginx" or leave it empty so users choosing
nginx can supply their own overrides).
- Introduced a new traefik-rbac.yaml template to define Role and RoleBinding for Traefik. - This configuration allows the Traefik controller to read secrets in the namespace, essential for loading TLS certificates referenced in IngressRoute. - The RBAC settings are conditionally applied based on the ingress configuration in values.yaml.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@charts/plane-enterprise/templates/ingress-traefik.yaml`:
- Around line 81-82: The template only sets tls.secretName ({{
.Values.ssl.tls_secret_name }}) for Traefik's websecure entrypoint and lacks
both an HTTP (web) IngressRoute with a redirect middleware and cert-manager
integration; update the chart documentation (README and values.yaml comments) to
(1) explain that this IngressRoute listens on the websecure entrypoint only and
instruct users to either add a companion IngressRoute for the web entrypoint
that references a redirect middleware or enable a Traefik static HTTP→HTTPS
redirect, and (2) explain the current requirement for a pre-existing secret
(tls.secretName) and provide migration steps for users coming from nginx's
ssl.generateCerts / ssl.createIssuer workflow—include sample steps to create a
cert-manager Issuer and Certificate that produce the expected secret or guidance
on modifying the template to support cert-manager annotations if they want
automated provisioning.
🧹 Nitpick comments (2)
charts/plane-enterprise/templates/traefik-rbac.yaml (2)
16-19: Role grants access to all secrets in the namespace — consider scoping down.The Role allows
get,list, andwatchon all secrets in the release namespace. This is broader than required; Traefik only needs to read the specific TLS secret. You can restrict this withresourceNames:Proposed scoping
rules: - apiGroups: [""] resources: ["secrets"] + resourceNames: ["{{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }}"] verbs: ["get", "list", "watch"]This follows the principle of least privilege and prevents the Traefik service account from reading unrelated secrets (e.g., database credentials) in the same namespace.
12-15: Conventionalmanaged-bylabel value isHelm(capitalized).The standard Helm-generated label uses
app.kubernetes.io/managed-by: Helm(capital H). Using lowercasehelmmay cause inconsistencies with labels on other resources managed by the same release.Also applies to: 26-29
| tls: | ||
| secretName: {{ default (printf "%s-ssl-cert" .Release.Name) .Values.ssl.tls_secret_name }} |
There was a problem hiding this comment.
No HTTP-to-HTTPS redirect or cert-manager integration.
Two things to be aware of:
-
HTTP→HTTPS redirect: This IngressRoute only listens on the
websecureentrypoint. There's no companion IngressRoute for theweb(HTTP) entrypoint with a redirect middleware, so HTTP requests will get connection-refused rather than a graceful redirect. This may need to be handled at the Traefik deployment level, but it's worth documenting. -
cert-manager: The nginx path supports
ssl.generateCerts/ssl.createIssuerfor automated certificate provisioning. This Traefik template only references a pre-existing secret. Users migrating from the nginx path with cert-manager will need manual steps.
Consider documenting these gaps in the chart's README or values.yaml comments.
🤖 Prompt for AI Agents
In `@charts/plane-enterprise/templates/ingress-traefik.yaml` around lines 81 - 82,
The template only sets tls.secretName ({{ .Values.ssl.tls_secret_name }}) for
Traefik's websecure entrypoint and lacks both an HTTP (web) IngressRoute with a
redirect middleware and cert-manager integration; update the chart documentation
(README and values.yaml comments) to (1) explain that this IngressRoute listens
on the websecure entrypoint only and instruct users to either add a companion
IngressRoute for the web entrypoint that references a redirect middleware or
enable a Traefik static HTTP→HTTPS redirect, and (2) explain the current
requirement for a pre-existing secret (tls.secretName) and provide migration
steps for users coming from nginx's ssl.generateCerts / ssl.createIssuer
workflow—include sample steps to create a cert-manager Issuer and Certificate
that produce the expected secret or guidance on modifying the template to
support cert-manager annotations if they want automated provisioning.
Description
Type of Change
Screenshots and Media (if applicable)
Test Scenarios
References
Summary by CodeRabbit
New Features
Chores