What goes wrong with unmanaged Helm
Helm is easy to start and easy to misuse. A chart is generated with helm create, the default boilerplate is left in place, and every team then bolts on its own conditionals. A year later there are twelve charts that look similar, behave differently, and nobody dares upgrade. Values files have hundreds of lines, half of which do nothing.
We write Helm charts as a product: small interface, safe defaults, tested, versioned. It is a core part of our GitOps, Helm, ArgoCD and Flux service, and the output is consumed by your GitOps controller rather than run by hand.
What a good chart looks like
- A values file a developer can understand in two minutes.
- Defaults that are safe: resource requests set, probes defined, non-root user, read-only root filesystem where the app allows it.
- A
values.schema.jsonthat rejects typos and wrong types. - Consistent labels and names so dashboards, alerts and
kubectlfilters work the same everywhere. - Upgrades that do not surprise you: no hidden changes to selectors or immutable fields.
How it works in practice
The values interface
We start from what developers need to change, not from everything Kubernetes can express. For a typical web service that is image and tag, replica count or autoscaling bounds, resource requests and limits, environment variables, ingress host, and a few feature flags. Everything else gets a sensible default.
# values.yaml (excerpt)
image:
repository: registry.example.com/payments/api
tag: "" # set by the pipeline, defaults to .Chart.AppVersion
replicaCount: 2
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 6
targetCPUUtilizationPercentage: 70
ingress:
enabled: true
host: payments.example.com
Setting resource requests and limits by default matters more than it looks: workloads with none are the first to be evicted and the hardest to schedule well.
Schema validation
A JSON schema stops mistakes at install time. Helm validates values against values.schema.json automatically on install, upgrade, lint and template:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["image"],
"properties": {
"replicaCount": { "type": "integer", "minimum": 1 },
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" }
}
}
}
}
This catches a quoted number where an integer was expected, or a misspelt key, long before it silently does nothing in production.
Helpers and consistency
Named templates in _helpers.tpl generate names, labels and selectors once. Selectors are the dangerous part: a Deployment selector is immutable, so a label change in a helper can break upgrades. We keep selector labels minimal and stable, and put everything else in ordinary labels.
Library charts for fleets of similar services
If you run thirty services that are all variations of “HTTP API with a database”, thirty copies of nearly identical templates is a liability. A library chart holds the shared logic. Each service chart becomes a few lines plus its own values. Fixing a probe default or adding a security setting is then one change, released as a new library version.
Testing and upgrade safety
Our standard pipeline for a chart:
helm lintwith strict mode on each values file.helm templatefor every environment, piped tokubeconformfor schema checks and to a policy tool such as kube-linter for common mistakes.- Unit tests for templates with conditionals using
helm unittest. - In CI, install into a throwaway cluster with chart-testing and run test hooks.
- A rendered diff posted to the pull request, so reviewers see what the change does to the manifests, not just to the templates.
That last step is the most valuable. Reviewing template changes is hard; reviewing the YAML they produce is easy.
Helm inside GitOps
With ArgoCD, Helm is a templating step: the controller renders the chart and applies the result, so helm list will not show the release. With Flux, a HelmRelease runs a real Helm install and keeps release history. Both approaches work, but they behave differently around hooks, rollbacks and drift, so we design the chart with the controller in mind. See how this connects to promotion across environments and publishing charts.
Decisions and trade-offs
Many values or few. Every option you expose is something you must document, test and support. We expose less than clients initially ask for, and add options when a second real use appears.
Subcharts and dependencies. Bundling a database as a subchart is convenient for demos and risky for production, because the lifecycle of the data is tied to the application release. We usually deploy stateful dependencies separately.
Hooks. Helm hooks for migrations or jobs can behave unexpectedly under GitOps controllers. Where possible we run migrations as explicit, observable jobs and design hooks carefully.
When not to write a chart. For a one-off internal tool deployed to a single environment, a handful of YAML files with Kustomize is easier to read. Do not add templating until you have something to template.
Common failure modes
- Templating everything. Charts full of
ifandwithbecome unreadable. Keep logic small. - Latest tags. Mutable tags make rollbacks impossible. We pin by version or digest.
- No rollout settings. Missing probes and disruption budgets cause outages during upgrades. Defaults include them.
- Secrets in values files. Never. Use Sealed Secrets or External Secrets Operator.
- Unversioned changes. A chart changed without a version bump deploys silently different behaviour. CI enforces version bumps.
Delivery and handover
You receive the charts in your repository, with schema, tests, README generated from values, and example values for each environment. Your developers make a change themselves during handover, usually adding a new service from the library chart, so the process is understood rather than inherited.
How to tell it is working
New services are a short values file rather than a copy of an old chart. Upgrades of the chart show a clear diff and rarely surprise anyone. Developers stop asking for help with YAML. If your charts have grown unruly, contact us for a review.