A Helm chart that worked fine with three configuration values often becomes unreadable past a dozen, once every new edge case adds another nested conditional to an already dense template. Understanding what Helm actually does, and where templating stops being a reasonable way to handle variation, keeps you from inheriting a chart nobody wants to touch anymore.
A chart is templates plus values
A Helm chart separates a deployment’s structure (templates, written in Go template syntax, that produce Kubernetes YAML) from its configuration (the values.yaml file, which supplies defaults, overridable at install time).
# templates/deployment.yaml (excerpt)
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
resources:
{{- toYaml .Values.resources | nindent 12 }}
# values.yaml (defaults)
replicaCount: 2
image:
repository: registry.example.com/app
tag: "1.4.2"
resources:
requests:
cpu: 250m
memory: 512Mi
helm template renders that mix into raw YAML without installing anything, which lets you check exactly what would be applied before any real deployment. It is often the first command worth knowing when a chart produces an unexpected result.
Where templating turns into a problem
A Go template supports conditionals and loops, which naturally pushes toward adding one per edge case encountered: one environment needs an extra environment variable, another disables a health probe, a third needs a specific annotation. Each addition is reasonable in isolation. After a dozen iterations, the template mixes the Kubernetes structure it generates with the conditional logic deciding that structure, and the two become hard to untangle when reading it.
{{- if and .Values.ingress.enabled (not .Values.ingress.legacy) }}
{{- if .Values.ingress.tls.enabled }}
{{- range .Values.ingress.hosts }}
{{- if ne .name "internal" }}
# ... the business logic has disappeared under the templating logic
{{- end }}
{{- end }}
{{- end }}
{{- end }}
The signal worth watching is not the template’s line count, but the nesting depth of its conditionals: past two or three levels, the question stops being “how do I add this case” and becomes “is this chart trying to cover too many distinct scenarios with one set of templates.”
Kustomize: composing instead of conditioning
Kustomize starts from a different principle: no templating, just a layering of patches on top of base YAML manifests. A base configuration describes the common case, and per-environment overlays apply only the differences.
# base/deployment.yaml — common case, no conditionals
# overlays/production/patch.yaml — only what differs
- op: replace
path: /spec/replicas
value: 5
This approach avoids the nested-conditional problem entirely, because there is never a logic branch to write: each environment gets its own overlay, readable independently of the others. The trade-off runs the other way: what a single Helm conditional expresses easily (“if X then Y”) requires duplicating a patch structure in Kustomize when the variation does not reduce to a simple value swap.
How to choose between the two
Helm fits well for a chart meant to be reused by other teams or published publicly (an official PostgreSQL or Redis chart, for instance), where configuration-by-values is exactly the point: the user never sees the templates, only the values they adjust. Kustomize fits better for internally managing several environments of a single application, where the logic stays simple and each overlay’s readability matters more than external reusability. The two tools combine without conflict, in fact: Kustomize can patch the already-rendered output of a Helm chart, for cases where a third-party chart does not expose exactly the parameter you need.
Where this fits
The packaging choice is part of the foundation laid during a Kubernetes migration: versioned, documented, reproducible, like the rest of the infrastructure. A chart that becomes unreadable after six months is no different from a bash script nobody dares to touch anymore, just newer.
What to remember
Helm separates templates and values to parametrize a deployment; the problem is never the presence of conditionals but their accumulation, which eventually mixes Kubernetes structure with templating logic to the point of becoming unreadable. Kustomize avoids that trap through patch composition instead of conditionals, at the cost of more duplication for complex variations. The deciding factor is the chart’s external reusability, not a tooling preference.