Helm can template Kubernetes Ingress resources, allowing you to dynamically configure routing rules based on your deployment’s needs.

Let’s see this in action. Imagine you have a web application deployed via Helm, and you want to expose it through an Ingress. You’ll define your Ingress in a templates/ingress.yaml file within your Helm chart:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:

  name: {{ include "mychart.fullname" . }}-ingress

  labels:

    {{- include "mychart.labels" . | nindent 4 }}

  annotations:
    # Example: Cert-manager annotation for automatic TLS
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:

  ingressClassName: {{ .Values.ingress.className }}

  rules:

    {{- range .Values.ingress.hosts }}


    - host: {{ .host | quote }}

      http:
        paths:

          {{- range .paths }}


          - path: {{ .path }}


            pathType: {{ .pathType }}

            backend:
              service:

                name: {{ $fullName := include "mychart.fullname" $ }}


                      {{ $serviceName := .serviceName | default $fullName }}


                      {{ $serviceName }}

                port:

                  number: {{ .port }}


          {{ end }}


    {{ end }}


  {{- if .Values.ingress.tls }}

  tls:

    {{- range .Values.ingress.tls }}

    - hosts:

        {{- range .hosts }}


        - {{ . | quote }}


        {{ end }}


      secretName: {{ .secretName }}


    {{ end }}


  {{ end }}

And your values.yaml might look like this:

ingress:
  className: nginx
  hosts:
    - host: myapp.example.com
      paths:
        - path: /api(/|$)(.*)
          pathType: Prefix
          serviceName: myapp-api-service
          port: 8080
        - path: /
          pathType: Prefix
          port: 80
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls-secret

When you run helm install myrelease ./mychart, Helm processes these files. It substitutes the placeholders like {{ include "mychart.fullname" . }}, {{ .Values.ingress.className }}, and loops through the hosts and paths defined in values.yaml to construct the final Ingress resource. The nginx.ingress.kubernetes.io/rewrite-target: /$2 annotation is a common example, telling the Nginx Ingress Controller to rewrite the URL path.

The primary problem this solves is managing the complexity of Ingress definitions across different environments or application versions. Instead of manually creating and updating Ingress YAML for each deployment, you can define a flexible template. This template allows you to specify hostnames, paths, backend services, and TLS configurations dynamically. For instance, you can easily change the host or add new paths by modifying values.yaml without touching the Ingress template itself. The {{- range .Values.ingress.hosts }} and nested {{- range .paths }} constructs are key here, enabling the creation of multiple host entries and path configurations from a list in your values.

Internally, Helm’s templating engine (Go’s text/template or html/template) evaluates these Go-style template directives. When you helm install or helm upgrade, Helm reads your values.yaml, merges it with default values, and then passes the combined data to the templating engine for each file in the templates/ directory. The engine renders the ingress.yaml file, replacing all the {{ ... }} expressions with their corresponding values from values.yaml. The resulting YAML is then sent to the Kubernetes API server. The include function, like {{ include "mychart.fullname" . }}, is a way to reference reusable template snippets defined in _helpers.tpl, promoting DRY principles within your chart.

The pathType field in Kubernetes Ingress is crucial for how path matching is performed. Prefix matches any URL path that starts with the specified path. Exact matches only the exact URL path. ImplementationSpecific delegates the matching behavior to the Ingress controller. When using Prefix with a trailing slash, like /api/, it will match /api/users but not /apiusers. However, if you need to capture parts of the URL for rewriting, as shown with path: /api(/|$)(.*) and nginx.ingress.kubernetes.io/rewrite-target: /$2, the Ingress controller’s specific annotations come into play. The (/|$) part of the regex ensures that the path matches either the end of the string or a slash, and (.*) captures any subsequent characters for rewriting.

Helm’s ability to conditionally include sections is also powerful for Ingress management. For example, you might only want to define TLS configuration if a specific value is set:


{{- if .Values.ingress.tlsEnabled }}

  tls:

    {{- range .Values.ingress.tls }}

    - hosts:

        {{- range .hosts }}


        - {{ . | quote }}


        {{ end }}


      secretName: {{ .secretName }}


    {{ end }}


{{ end }}

This allows you to deploy an Ingress without TLS in development or internal environments and enable it for production by simply toggling ingress.tlsEnabled in your values.yaml.

The specific behavior of annotations like nginx.ingress.kubernetes.io/rewrite-target is entirely dependent on the Ingress controller you are using (e.g., Nginx, Traefik, HAProxy). Helm itself just injects these annotations into the Ingress object; it’s the controller that interprets them. This separation of concerns means you can use Helm to template any annotation relevant to your chosen Ingress controller.

The next step in managing ingress with Helm is often incorporating advanced routing logic, such as traffic splitting for canary deployments or blue/green deployments, typically managed through separate Custom Resource Definitions (CRDs) that your Ingress controller might support.

Want structured learning?

Take the full Helm course →