Definition
Helm is the package manager for Kubernetes.
- Chart – a versioned bundle of templated Kubernetes manifests plus a default
values.yaml. - Templates – use Go templating (e.g.
{{ .Values.replicaCount }}) to substitute values, so one chart renders different manifests per environment. - Values – defaults live in
values.yaml; environment overrides invalues-dev.yaml,values-prod.yaml. - Versioning –
versionin Chart.yaml is the chart/package version;appVersionis the application image version. - Lifecycle –
helm installcreates a release,helm upgraderecords a new revision,helm rollbackreverts to a previous one.
At the National Bank of India, imagine the blueprint for setting up a new branch: the counters, the vault, the signage. Writing that blueprint by hand for every city is error-prone. Helm is a reusable branch blueprint where the city-specific details, like branch size and number of counters, are filled in from a small settings sheet. Dev gets a two-counter branch, production gets a fifty-counter branch, from the same blueprint. If a new branch setup goes wrong, Helm lets you revert to the previous working layout in one command.
Understanding the Concepts
What is a Helm chart?
A chart is a directory with a Chart.yaml describing its name and versions, a templates/ folder of manifest templates, and a values.yaml with defaults. Templates reference values with Go template expressions.
transfer-chart/
Chart.yaml # name, version, appVersion
values.yaml # default values
values-dev.yaml # dev overrides
values-prod.yaml # production overrides
templates/
deployment.yaml # templated Deployment
service.yaml # templated Service
ingress.yaml # templated Ingress
_helpers.tpl # named template helpers
# Chart.yaml
apiVersion: v2
name: transfer-service
version: 1.3.0 # chart/package version
appVersion: "2.1.0" # application image version
description: Helm chart for the NBI Transfer Service
How does templating work?
Templates use Go templating to substitute values from the values file. Conditionals, loops, and named templates in _helpers.tpl are supported.
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-transfer
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}-transfer
template:
metadata:
labels:
app: {{ .Release.Name }}-transfer
spec:
containers:
- name: transfer
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.service.targetPort }}
resources:
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
# values.yaml - defaults
replicaCount: 2
image:
repository: nbi-registry.example.com/transfer-service
tag: "2.1.0"
service:
type: ClusterIP
port: 80
targetPort: 8080
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
How do you parameterize per environment?
You keep base defaults in values.yaml and layer environment-specific overrides. At deploy time you select the right overlay with the -f flag.
# values-dev.yaml
replicaCount: 1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
# values-prod.yaml
replicaCount: 10
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
cpu: "2"
memory: 1Gi
ingress:
enabled: true
host: api.nbi.com
# Deploy to dev
helm upgrade --install transfer-dev ./transfer-chart \
-f values-dev.yaml -n dev
# Deploy to production
helm upgrade --install transfer-prod ./transfer-chart \
-f values-prod.yaml -n prod
How does versioning work?
Versioning is two-dimensional. The chart version follows semantic versioning and changes when the chart's templates or defaults change. The appVersion tracks the application image. Keeping them distinct lets you ship a chart fix without bumping the app, or roll out a new app image without touching chart structure.
# Chart.yaml - chart fix adds a readiness probe, app unchanged
version: 1.4.0 # bumped: chart templates changed
appVersion: "2.1.0" # unchanged: same application image
# Chart.yaml - new app release, chart unchanged
version: 1.4.0 # unchanged: no chart template changes
appVersion: "2.2.0" # bumped: new application image
# Publish the chart to an OCI registry
helm package ./transfer-chart
helm push transfer-service-1.4.0.tgz oci://nbi-registry.example.com/charts
# Consume by version
helm install transfer oci://nbi-registry.example.com/charts/transfer-service \
--version 1.4.0
How do install, upgrade, and rollback work?
The lifecycle commands map to tracked release revisions.
# Install creates release at revision 1
helm install transfer ./transfer-chart -f values-prod.yaml -n prod
# NAME: transfer
# STATUS: deployed
# REVISION: 1
# Upgrade applies changes and records revision 2
helm upgrade transfer ./transfer-chart -f values-prod.yaml -n prod
# Release "transfer" has been upgraded. Revision: 2
# History shows all revisions
helm history transfer -n prod
# REVISION STATUS CHART APP VERSION
# 1 superseded transfer-service-1.3.0 2.1.0
# 2 deployed transfer-service-1.4.0 2.2.0
# Rollback to revision 1
helm rollback transfer 1 -n prod
# Rollback was a success! Revision: 3 (copy of revision 1)
What is the --atomic flag?
The --atomic flag tells Helm to automatically roll back if an upgrade fails. This is essential for banking releases where a half-applied upgrade is worse than no upgrade.
# Production deploy with automatic rollback on failure
helm upgrade --install transfer-prod ./transfer-chart \
-f values-prod.yaml \
-n prod \
--atomic \
--timeout 5m
# If the new Pods fail readiness within 5 minutes,
# Helm automatically rolls back to the previous revision
What are common mistakes?
The most common mistake is not using --atomic for production, which can leave a half-broken release that requires manual intervention.
# WRONG - upgrade without --atomic
helm upgrade transfer-prod ./transfer-chart -f values-prod.yaml -n prod
# If the new version crashes, the release is stuck in "failed" state
# Manual rollback needed: helm rollback transfer-prod 1 -n prod
# CORRECT - always use --atomic for production
helm upgrade --install transfer-prod ./transfer-chart \
-f values-prod.yaml -n prod --atomic --timeout 5m
Another mistake is relying on Helm rollback to undo database migrations run by Helm hooks. Helm rollback reverts Kubernetes objects but does not undo a migration hook's SQL.
How do you preview changes before applying?
Helm provides several safety tools for reviewing changes before applying them.
# Lint the chart for syntax and best-practice issues
helm lint ./transfer-chart -f values-prod.yaml
# Render templates locally without applying
helm template transfer ./transfer-chart -f values-prod.yaml
# Dry-run against the cluster (validates with API server)
helm upgrade --install transfer ./transfer-chart \
-f values-prod.yaml --dry-run
# Show the diff between current and proposed (requires helm-diff plugin)
helm diff upgrade transfer ./transfer-chart -f values-prod.yaml
What should a senior engineer know?
Helm rollback, like a Deployment rollback, reverts the Kubernetes objects Helm manages but does not undo external side effects such as a database migration run by a Helm hook. Design migrations to be backward compatible so rollbacks remain safe.
Charts should be published to an OCI-compliant registry and consumed by pinned version so every deployment is reproducible and auditable.
For multi-team organizations, a chart library pattern lets teams share common templates (labels, security context, resource defaults) without copying YAML.
# Chart dependencies for shared libraries
# Chart.yaml
dependencies:
- name: nbi-common
version: 1.0.0
repository: oci://nbi-registry.example.com/charts
Visual Overview
Helm Chart Structure
=====================
transfer-chart/
├── Chart.yaml version: 1.4.0, appVersion: 2.2.0
├── values.yaml defaults (replicas: 2, cpu: 250m)
├── values-dev.yaml dev overrides (replicas: 1)
├── values-prod.yaml prod overrides (replicas: 10)
└── templates/
├── deployment.yaml {{ .Values.replicaCount }}
├── service.yaml {{ .Values.service.port }}
├── ingress.yaml {{ .Values.ingress.host }}
└── _helpers.tpl shared named templates
Helm Release Lifecycle
=======================
helm install helm upgrade helm rollback
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Rev 1 │ → │ Rev 2 │ → │ Rev 3 │
│ v1.3.0 │ │ v1.4.0 │ │ (copy of │
│ app 2.1 │ │ app 2.2 │ │ Rev 1) │
│ deployed │ │ deployed │ │ deployed │
└──────────┘ └──────────┘ └──────────┘
▲ │
└─────────── rollback restores ────────────┘
Key Takeaway
Helm is the package manager for Kubernetes. A chart is a versioned bundle of templated manifests plus a default values file. Templates use Go templating to substitute values, so one chart can render a two-replica Deployment for dev or a ten-replica Deployment for production, purely by supplying a different values file. That eliminates configuration drift across environments.
Versioning is two-dimensional. The chart version tracks the package itself, and the appVersion tracks the application image. Keeping them separate means you can ship a chart fix without pretending the application changed, and vice versa. Charts published to a registry and consumed by pinned version make every deployment reproducible.
The lifecycle commands map to tracked revisions. Install creates a release at revision 1, upgrade records a new revision, and rollback reverts to a chosen revision. The --atomic flag is essential for production because it automatically rolls back a failed upgrade instead of leaving a half-broken release.
Remember one sentence: Helm templates one chart into many environments with values files, tracks every release as a numbered revision, and rolls back in one command.
---