helm chartingress
In Kubernetes, exposing services to external traffic is a common operational challenge. While a Service abstracts internal pod access, an Ingress resource defines how external HTTP and HTTPS requests reach those services. Managing Ingress manifests by hand across development, staging, and production quickly becomes error-prone. Helm, the package manager for Kubernetes, solves this by packaging Ingress definitions alongside application workloads into versioned, parameterized charts. This article explains how to model Ingress resources inside a Helm Chart, how to drive behavior through values.yaml, and what patterns make multi-environment routing predictable.
Helm treats a Chart as a collection of files that describe a set of Kubernetes resources. A typical layout looks like this:
mychart/
├── Chart.yaml
├── values.yaml
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
└── _helpers.tpl
Chart.yaml holds metadata such as name, version, and description. values.yaml provides default configuration values. The templates directory contains Go-templated manifest files. During rendering, Helm injects values from values.yaml (or from command-line overrides) into these templates, producing standard Kubernetes YAML. This “one template, many configurations” model is what makes Helm suitable for promoting the same application through multiple environments.
Inside templates, values are referenced with {{ .Values.xxx }}. Helper templates defined in _helpers.tpl can generate reusable snippets such as full resource names or standard label sets, which keeps individual manifest files concise.
What Is Kubernetes Ingress?
An Ingress is an API object that manages external access to services, typically over HTTP and HTTPS. It does not function alone; an Ingress Controller—such as NGINX Ingress Controller, Traefik, or Istio—must be running in the cluster to interpret the Ingress rules and program the underlying load balancer or proxy.
An Ingress resource commonly handles:
- Routing requests to different Services based on host or path.
- Terminating TLS and managing certificates through Kubernetes Secrets.
- Enabling controller-specific behavior through annotations, such as rate limiting, request rewriting, IP allowlisting, or custom timeouts.
Because routing requirements vary by environment, hardcoding Ingress manifests is rarely practical. A development cluster might use a wildcard hostname and self-signed certificates, while production requires strict domain names, managed TLS secrets, and specific ingress classes.
Authoring an Ingress Template in a Helm Chart
A production-grade chart should treat the Ingress as optional and environment-aware. The following example shows a typical templates/ingress.yaml:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- toYaml .Values.ingress.tls | nindent 4 }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "mychart.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
Several patterns deserve attention:
- The outer
{{- if .Values.ingress.enabled }}block makes the entire Ingress optional. Settingingress.enabledtofalseprevents Helm from rendering the resource, which is useful for environments that rely on alternative traffic entry points. - The
rangedirective iterates overhostsandpaths, allowing an arbitrary number of routing rules without touching the template. - Inside a nested
range, the dot.refers to the current list item. To reach values outside the current scope, such as the service port, the chart uses$, as in$.Values.service.port. - Annotations and TLS blocks are passed through with
toYaml, keeping the template short and making it easy to add new keys without editing the manifest.
Parameterizing Ingress with values.yaml
The template above expects an ingress section in values.yaml. A sensible default might look like this:
ingress:
enabled: true
className: "nginx"
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
tls:
- secretName: mychart-tls
hosts:
- app.example.com
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
At install time, operators can override individual fields:
# Disable Ingress entirely in a test namespace
helm install myapp ./mychart --set ingress.enabled=false
# Point production traffic to a dedicated domain and certificate
helm upgrade myapp ./mychart \
--set ingress.hosts[0].host=prod.example.com \
--set ingress.tls[0].secretName=prod-tls
For more complex environments, loading a dedicated values file is usually preferable to long --set chains:
helm upgrade myapp ./mychart -f values-production.yaml
Keeping environment-specific values under version control enables audit trails, peer review, and rollback. A change to routing rules becomes a pull request rather than an imperative cluster mutation.
Supporting Multiple Hosts and Paths
When an application exposes both a web frontend and an API backend, the chart needs no template changes. The operator simply extends the hosts list:
ingress:
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
- host: api.example.com
paths:
- path: /v1
pathType: Prefix
Helm renders a separate rules entry for every host. This data-driven approach means the same chart can serve a simple single-domain deployment and a complex multi-service topology without divergence.
Common Issues and Best Practices
Even with a well-structured chart, several operational pitfalls recur:
- Ingress exists but traffic does not reach the service. Verify that an Ingress Controller is installed and that
ingressClassNamematches the controller’s class. Without a controller, an Ingress resource is only a declarative object with no implementation. - Template rendering errors. Run
helm template ./mychartorhelm lintlocally before installing. These commands surface syntax mistakes and missing values without touching the cluster. - Path matching behaves unexpectedly. Understand the difference between
PrefixandExactpath types.Prefixmatches any path beginning with the specified string, whileExactrequires an identical match. - API version mismatches.
networking.k8s.io/v1requires Kubernetes 1.19 or later. Older clusters may still use `extensions