Definition
- Chart – a versioned, parameterized package of Kubernetes manifests with Go templates.
- Values – a YAML file that fills template placeholders; a base values file plus small per-environment overrides.
- upgrade --install – idempotent: installs if absent, upgrades if present, recording a numbered revision.
- Rollback – restores a previous revision in one command;
--atomicauto-rolls back on failure. - Named templates – reusable helpers to avoid repetition across manifests.
- Chart dependencies – a chart can bundle sub-charts (e.g., a service plus its Redis).
Imagine National Bank of India opens branches in many cities. Raw manifests are like handing each branch a thick stack of fully filled-out forms, copying the whole stack for each city and manually white-outing the city name and vault size. Helm is one master template with blanks, plus a small city-specific sheet that fills only the blanks. One template, a tiny per-city override, and every branch is set up consistently. Helm also keeps a logbook of every version, so you can flip back to a previous version in one step.
Understanding the Concepts
What problem does Helm solve?
With raw manifests you write static YAML and apply with kubectl apply. At scale this creates copy-paste drift: the same service in dev, staging, and production means three copies of the manifests with hand-edited differences that silently diverge.
# Raw manifest problem: three copies that drift apart
# dev/deployment.yaml: replicas: 1, image: bank/loan-service:dev
# staging/deployment.yaml: replicas: 2, image: bank/loan-service:staging
# prod/deployment.yaml: replicas: 5, image: bank/loan-service:v1.2.3
# A memory-limit change in prod but not staging causes crashes nobody can reproduce
How does Helm templating work?
A chart contains templates with placeholders. A values file fills them. One template serves every environment.
# chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
resources:
requests:
memory: {{ .Values.resources.requests.memory }}
cpu: {{ .Values.resources.requests.cpu }}
limits:
memory: {{ .Values.resources.limits.memory }}
cpu: {{ .Values.resources.limits.cpu }}
env:
- name: JAVA_OPTS
value: {{ .Values.javaOpts | quote }}
# chart/values.yaml (base defaults)
replicaCount: 1
image:
repository: bank/loan-service
tag: latest
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
javaOpts: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
# chart/values-prod.yaml (production overrides -- only what differs)
replicaCount: 5
image:
tag: abc123
resources:
requests:
memory: "2Gi"
cpu: "2000m"
limits:
memory: "2Gi"
cpu: "2000m"
javaOpts: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError"
How does upgrade --install work?
helm upgrade --install is idempotent: it installs if the release does not exist, upgrades if it does. It records each deploy as a numbered revision.
# Deploy to production
helm upgrade --install loan-service ./chart \
-f chart/values-prod.yaml \
--set image.tag=abc123 \
--timeout 5m
# First run: installs (revision 1)
# Second run: upgrades (revision 2)
# Third run: upgrades (revision 3)
# View release history
helm history loan-service
# REVISION STATUS DESCRIPTION
# 1 deployed Install complete
# 2 deployed Upgrade complete
# 3 deployed Upgrade complete
How does rollback work?
Because Helm keeps revision history, you can restore a previous known-good revision in one command.
# Rollback to revision 2
helm rollback loan-service 2
# Kubernetes is restored to exactly the manifests of revision 2
# Atomic upgrade: auto-rollback if readiness checks fail
helm upgrade --install loan-service ./chart \
-f chart/values-prod.yaml \
--set image.tag=bad-image \
--timeout 5m \
--atomic
# If pods fail readiness within 5 minutes:
# Helm automatically rolls back to the previous good revision
# Cluster is in a known-good state, not half-upgraded
What is the difference between Helm and raw manifests?
| Aspect | Raw kubectl apply | Helm |
|--------|-------------------|------|
| Templating | None (static YAML) | Go templates + values |
| Multi-environment | Copy and edit each file | One chart + small value overrides |
| Release concept | None (just objects) | Named release with revision history |
| Rollback | Reconstruct old YAML from Git | helm rollback |
| Idempotent deploy | kubectl apply (partial) | helm upgrade --install (full) |
| Drift prevention | Manual discipline | Shared templates prevent divergence |
What is the difference between Helm and Kustomize?
| Aspect | Helm | Kustomize |
|--------|------|-----------|
| Approach | Go templates + values | Base + overlays (patching) |
| Templating | Full Go template language | No templates, patch-based |
| Release management | Built-in (revisions, rollback) | None (just manifest generation) |
| Complexity | Higher (template language) | Lower (YAML patches) |
| Best for | Multi-env deploys with rollback | Simple overlays without release mgmt |
# Helm: template-based
helm upgrade --install loan-service ./chart -f values-prod.yaml
# Kustomize: overlay-based
kubectl apply -k overlays/production/
When do you reach for Helm?
Use Helm when:
- Deploying the same app across multiple environments (dev/staging/prod)
- You need atomic releases with revision history and one-command rollback
- You package your service for others to install
- You consume third-party software distributed as charts (Prometheus, Nginx)
Consider alternatives when:
- Single simple app in one environment (raw manifests or Kustomize)
- Team dislikes Go templating (Kustomize overlays)
- GitOps with ArgoCD (either works, but Kustomize is lighter)
What are common mistakes with Helm?
# WRONG: hard-coding values in templates instead of using values.yaml
# chart/templates/deployment.yaml:
spec:
replicas: 5 # Hard-coded -- defeats the purpose of templating
# CORRECT: use values
spec:
replicas: {{ .Values.replicaCount }}
# WRONG: deploying without --atomic (half-upgraded state on failure)
helm upgrade --install loan-service ./chart
# If new pods crash, cluster is left half-upgraded
# CORRECT: use --atomic for safe upgrades
helm upgrade --install loan-service ./chart --atomic --timeout 5m
# Failed upgrade auto-rolls back to the last good revision
What should a senior engineer know?
Helm is not always the answer. For a single simple app in a single environment, raw manifests or Kustomize can be simpler. But for a bank running many services across dev, staging, and production with strict rollback requirements, Helm's release and rollback model earns its keep. The --atomic flag is essential in production because it ensures a failed upgrade does not leave the cluster in a broken state.
Visual Overview
Raw Manifests vs Helm
======================
Raw manifests:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ dev/ │ │ staging/ │ │ prod/ │
│ deploy. │ │ deploy. │ │ deploy. │
│ yaml │ │ yaml │ │ yaml │
│ (copy) │ │ (copy) │ │ (copy) │
└──────────┘ └──────────┘ └──────────┘
Three copies that silently drift apart
Helm:
┌──────────────────────┐
│ chart/ │
│ templates/ │ One template
│ values.yaml │ Base defaults
│ values-dev.yaml │ Tiny override
│ values-staging.yaml │ Tiny override
│ values-prod.yaml │ Tiny override
└──────────────────────┘
Shared templates prevent divergence
Helm Release Lifecycle
=======================
helm upgrade --install
│
▼
Revision 1 ──▶ Revision 2 ──▶ Revision 3
│
(bad image)
│
helm rollback 2
│
▼
Revision 4 (= state of rev 2)
Cluster is healthy
Key Takeaway
Helm wraps Kubernetes manifests into a versioned, parameterized chart. Instead of copying YAML for each environment and editing by hand, you maintain one chart with templates and small per-environment value files that override only what differs. This eliminates copy-paste drift where a change lands in production but not staging.
Helm's release management means upgrade --install is idempotent with a revision history, and rollback restores a previous known-good state in one command. The --atomic flag is essential in production: if new pods fail readiness checks, Helm automatically rolls back instead of leaving the cluster half-upgraded.
Reach for Helm when deploying across multiple environments, when you need atomic releases with rollback, or when consuming third-party charts. For a single app in one environment, raw manifests or Kustomize may be simpler. At National Bank of India, converting our payments-service to Helm eliminated config drift between environments and turned a 15-minute manual rollback into a one-command operation.
Remember one sentence: Helm turns per-environment YAML copies into one templated chart with values overrides, giving you atomic releases and one-command rollback.
---