Ingress on Kestrel
All external HTTP and HTTPS traffic enters Kestrel through Traefik, the cluster’s sole ingress controller. There is no cloud load balancer pool and no alternative ingress class — every tenant uses the same shared edge. This page covers what the Ingress resource looks like, how hostnames work, and how TLS is automated.
ingressClassName is required
Section titled “ingressClassName is required”Every Ingress must set ingressClassName: traefik. Omitting the field or using a different value results in the Ingress being silently ignored — Traefik only watches resources that explicitly name it. There is no default ingress class configured at the cluster level, so “leave it blank and hope” does not work.
Hostname format
Section titled “Hostname format”Each tenant is assigned a subdomain of kestrel.arbutus.cloud. Tenant hostnames follow the pattern:
<your-tenant>.kestrel.arbutus.cloud*.<your-tenant>.kestrel.arbutus.cloudFor example, if your tenant is def-profname and your application is my-service, the hostname is my-service.def-profname.kestrel.arbutus.cloud. The bare tenant hostname (def-profname.kestrel.arbutus.cloud) is also available. There is no strict enforcement of this naming convention at admission time, but deviating from it makes traffic harder to trace in Grafana and support tickets harder to triage.
Hostname collision scope
Section titled “Hostname collision scope”Capsule enforces hostnameCollisionScope: Tenant — an Ingress whose host already exists on another Ingress within the same tenant is rejected at admission. Two different tenants cannot shadow each other because cross-tenant routing is already walled off at the network layer.
If you are migrating traffic from one namespace to another inside your tenant, delete the old Ingress first (or temporarily change the hostname on the stale copy) before creating the new one. See Known limitations — Hostname collision scope for the Capsule field that controls this.
Wildcard hostnames are blocked
Section titled “Wildcard hostnames are blocked”An Ingress with a wildcard host (e.g. host: "*.<your-tenant>.kestrel.arbutus.cloud") is rejected at admission by Capsule (allowWildcardHostnames: false). Wildcard hostnames would let one tenant shadow every subdomain under a parent, which breaks the per-host routing guarantees the shared edge depends on.
Use one Ingress (or one Ingress rule) per concrete hostname. If you have a legitimate need for a wildcard range — for example, a platform-style tenant that owns a subtree — open a ticket with RCS and they can add an explicit allow. See Known limitations — Wildcard hostnames.
TLS via cert-manager
Section titled “TLS via cert-manager”TLS certificates are issued automatically by cert-manager using the letsencrypt-prod cluster issuer. Add the annotation and the tls block to your Ingress and cert-manager handles the ACME challenge, certificate issuance, and renewal. No manual certificate management is needed for hosts under your tenant’s kestrel.arbutus.cloud subdomain — the letsencrypt-prod cluster issuer is restricted to those names. Custom domains require your own namespace-scoped Issuer; see TLS certificates for both paths.
The annotations that trigger cert-manager and configure Traefik TLS:
annotations: cert-manager.io/cluster-issuer: letsencrypt-prod traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.tls: "true"The tls block names the host and the Secret where the certificate will be stored:
tls: - hosts: - my-service.<your-tenant>.kestrel.arbutus.cloud secretName: my-service-tlscert-manager creates the Secret automatically — you do not need to pre-create it. The certificate renews before expiry without intervention. See networking/tls for the full cert-manager details, including what tenants do and do not control.
Complete Ingress example
Section titled “Complete Ingress example”A minimal working Ingress with TLS. Replace <your-tenant> with your tenant name and my-service with your application name.
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: my-service namespace: <your-tenant>-prod annotations: cert-manager.io/cluster-issuer: letsencrypt-prod traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.tls: "true"spec: ingressClassName: traefik tls: - hosts: - my-service.<your-tenant>.kestrel.arbutus.cloud secretName: my-service-tls rules: - host: my-service.<your-tenant>.kestrel.arbutus.cloud http: paths: - path: / pathType: Prefix backend: service: name: my-service port: number: 80This Ingress routes https://my-service.<your-tenant>.kestrel.arbutus.cloud/ to the my-service ClusterIP Service on port 80. The backend Service must be type: ClusterIP — LoadBalancer and NodePort are blocked. See Service types for why and what to use instead.
Where Traefik sits in the traffic path
Section titled “Where Traefik sits in the traffic path”Traefik runs in the traefik namespace as a Deployment. Kestrel has no in-cluster load balancer (no kube-vip); external traffic enters the cluster through Cloudflare and is routed to Traefik. The default-deny NetworkPolicy in every tenant namespace includes a pre-approved allow rule for Traefik’s pods, so your workload receives traffic from the ingress controller without any additional NetworkPolicy on your side. See Network model for the full picture of default-deny plus the four pre-approved allow rules.