Introduction to Kubernetes Helm Charts
Learn what Helm charts are, how templates and values work together, and how to package your own application for repeatable, parameterized Kubernetes deployments.
What you'll learn
- ✓What problem Helm solves
- ✓Chart structure and templating syntax
- ✓Values, overrides, and environments
- ✓Releases, rollbacks, and upgrades
- ✓When Helm is the wrong tool
Prerequisites
- •Basic familiarity with Kubernetes manifests
What and Why
Hand-writing a separate set of YAML manifests for dev, staging, and production gets old fast. You end up with subtle drift, copy-paste mistakes, and brittle sed pipelines in your CI. Helm is Kubernetes’ package manager. A chart is a parameterized bundle of manifests; a release is an instance of that chart installed in a cluster.
Helm turns “10 YAML files copy-pasted three times” into “one chart, three values files.”
Mental Model
A chart is a directory with templates and a default values file. At install time, Helm renders the templates by substituting values, then sends the result to the Kubernetes API. The state of each release is stored as a Secret in the target namespace, which is how Helm supports upgrades and rollbacks.
Chart/ values.yaml -overrides-> values-prod.yaml
templates/ \ /
deployment.yaml \ /
service.yaml v v
ingress.yaml [helm template]
|
v
[rendered manifests]
|
v
kubectl apply (via Helm)
|
v
[release stored as Secret] Hands-on Example
Scaffold a chart:
helm create myapp
You get this layout:
myapp/
Chart.yaml
values.yaml
templates/
deployment.yaml
service.yaml
ingress.yaml
_helpers.tpl
Trimmed values.yaml:
replicaCount: 2
image:
repository: myorg/myapp
tag: "1.0"
service:
type: ClusterIP
port: 80
ingress:
enabled: false
host: app.example.com
templates/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels: {{- include "myapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels: {{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels: {{- include "myapp.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 80
Install once, override per environment:
helm install myapp ./myapp -f values-prod.yaml \
--set image.tag=1.4.2 --namespace prod
Upgrade later:
helm upgrade myapp ./myapp -f values-prod.yaml \
--set image.tag=1.4.3 --namespace prod
Roll back if something breaks:
helm history myapp -n prod
helm rollback myapp 4 -n prod
Common Pitfalls
Treating templates like code. Go templating gets ugly fast with conditionals nested inside conditionals. If your chart has more {{ if }} than YAML, consider Kustomize or a thin code-based tool (Pulumi, cdk8s) instead.
Embedding secrets in values.yaml. Never commit production secrets. Use --set from CI, Helm secrets plugins, or an external secret operator.
Ignoring chart versioning. Chart.yaml has version (chart) and appVersion (app). Bump the chart version on every change; consumers rely on it for rollback.
Forgetting helm lint. Run it in CI. It catches templating errors before they hit a cluster.
CRD upgrades. Helm installs CRDs once and never updates them by default. Charts with CRDs need a deliberate upgrade procedure.
--force on upgrade. It deletes and recreates resources, causing downtime. Almost never the right answer.
Practical Tips
Use helm template to render locally without touching a cluster:
helm template myapp ./myapp -f values-prod.yaml > rendered.yaml
Inspect rendered.yaml before any production rollout, especially when changing template logic.
Pull from public repos to avoid reinventing wheels:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install postgres bitnami/postgresql -f my-postgres-values.yaml
For multi-environment workflows, structure values files like:
charts/myapp/
values.yaml # defaults
values-dev.yaml
values-staging.yaml
values-prod.yaml
Each environment file overrides only what differs. Keep defaults safe.
Combine with GitOps tools like Argo CD or Flux. Both natively understand Helm and let you commit values files to Git while the controller reconciles releases continuously.
For complex apps, embrace library charts for reusable templates and subcharts for shared dependencies. But fight the temptation to build a generic chart that handles every app in your org — those become unmaintainable.
Wrap-up
Helm makes Kubernetes deployments repeatable, parameterized, and rollback-friendly. Start by scaffolding a chart, keep templates simple, separate environments with values files, and use helm template and helm lint as part of your CI. When templating gets out of hand, reach for Kustomize overlays or a typed alternative. Used with discipline, Helm is the lowest-friction way to ship the same app to dev, staging, and production without the YAML copy-paste tax.
Related articles
- Kubernetes Kubernetes Stateful vs Stateless Workloads
Understand when to use Deployments vs StatefulSets, what stable identity buys you, and how to operate stateful workloads safely on Kubernetes.
- Kubernetes Helm Basics: Kubernetes Package Manager
Learn Helm fundamentals: helm install, charts, values.yaml, templating, releases, and when to choose Helm over plain Kubernetes YAML manifests.
- Kubernetes Kubernetes Cluster Upgrades and Pod Eviction Explained
How Kubernetes cluster upgrades drain nodes, how pod eviction works, and how PodDisruptionBudgets and graceful shutdown keep workloads safe during upgrades.
- Kubernetes Kubernetes ConfigMaps and Secrets Tutorial
A practical walkthrough of ConfigMaps and Secrets in Kubernetes, including how to inject them as environment variables, mount as files, and rotate safely.