Helm Package Management¶
Overview¶
Raw YAML manifests do not scale. A microservice platform might deploy hundreds of objects — Deployments, Services, Ingress rules, HPAs, ServiceAccounts, and ConfigMaps — across environments with slightly different values. Helm is the CNCF package manager for Kubernetes: it bundles manifests into charts, parameterizes them with values, and tracks releases with version history, rollback, and upgrade semantics.
Platform and DevOps engineers use Helm to ship internal platforms, deploy third-party software (Prometheus, ingress-nginx, cert-manager), and enforce consistent labels, probes, and resource limits. GitOps workflows (Argo CD, Flux) often render Helm charts as the deployment source of truth. This tutorial teaches chart structure, templating, release lifecycle, and production patterns.
This is Tutorial 15 in Module 5: Security & Tooling of the REBASH Academy Kubernetes series. Complete Troubleshooting Kubernetes Workloads first. This tutorial follows our documentation standards.
Prerequisites¶
- Completed Troubleshooting Kubernetes Workloads
- kubectl configured against a running cluster
- Helm 3 installed (
helm versionshows v3.x) - Understanding of YAML, Deployments, Services, and Ingress from prior tutorials
- Optional: Helm repos accessible from your network (Artifact Hub, Bitnami)
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Explain Helm charts, releases, revisions, and values files
- Install, upgrade, rollback, and uninstall Helm releases
- Create a chart with
helm createand customize templates - Use Go templating, conditionals, and
values.yamloverrides - Manage environment-specific configs with
-f values-dev.yamllayering - Lint and dry-run charts before applying to production clusters
- Install charts from public repositories and inspect rendered manifests
- Apply Helm best practices for secrets, labels, and chart versioning
Architecture Diagram¶
Helm client renders chart templates + values into manifests, then applies them to the cluster as a tracked release.
flowchart LR
DEV["Developer / CI"]
CHART["Helm Chart<br/>templates + values"]
HELM[Helm CLI]
API[Kubernetes API]
REL["Release<br/>revision history"]
DEV -->|helm upgrade --install| HELM
CHART --> HELM
HELM -->|render + apply| API
HELM -->|store metadata| REL
REL -->|helm rollback| HELM Theory¶
Helm 3 Architecture¶
Helm 3 removed Tiller — the server-side component from Helm 2. Today:
| Component | Role |
|---|---|
| Helm CLI | Local client; renders templates and calls Kubernetes API |
| Chart | Directory of templates, values, and metadata |
| Release | Named instance of a chart deployed to a cluster |
| Revision | Numbered snapshot of a release on upgrade |
Release metadata lives in Secrets (default) or ConfigMaps in the release namespace — helm history reads this store.
Chart Structure¶
mychart/
├── Chart.yaml # Chart metadata (name, version, appVersion)
├── values.yaml # Default configuration values
├── charts/ # Dependent subcharts
├── templates/ # Kubernetes manifests with Go templates
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl # Named template partials
│ └── NOTES.txt # Post-install notes
└── .helmignore
Chart.yaml version follows SemVer — increment on chart changes. appVersion tracks the application image version for documentation; it does not auto-update images unless templates reference it.
Values and Overrides¶
values.yaml provides defaults. Override at install time:
helm upgrade --install myapp ./mychart \
-f values.yaml \
-f values-prod.yaml \
--set replicaCount=5 \
--set image.tag=v2.1.0 \
-n production --create-namespace
Later files and --set take precedence. This layering supports dev/staging/prod without duplicating charts.
Templating Basics¶
Templates use Go text/template with Sprig functions:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
{{- if .Values.resources }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
Key concepts:
| Syntax | Meaning |
|---|---|
{{ .Values.replicaCount }} | Value from values.yaml |
{{ .Chart.Name }} | Chart metadata |
{{ .Release.Name }} | Release name from install command |
{{- if .Values.ingress.enabled }} | Conditional resource |
{{- include "mychart.labels" . }} | Reusable partial from _helpers.tpl |
Release Lifecycle¶
helm install myrelease ./chart # revision 1
helm upgrade myrelease ./chart # revision 2
helm history myrelease
helm rollback myrelease 1 # back to revision 1
helm uninstall myrelease
helm upgrade --install (alias helm upgrade -i) is idempotent — creates if missing, upgrades if exists. CI/CD pipelines should always use upgrade --install.
Chart Repositories¶
Helm charts publish to HTTP repositories or OCI registries:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo nginx
helm install my-nginx bitnami/nginx -n web --create-namespace
Artifact Hub (artifacthub.io) indexes public charts. Verify publisher trust before production installs.
Dependencies (Subcharts)¶
Chart.yaml dependencies:
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
Run helm dependency update to vendor subcharts into charts/. Parent values pass to subcharts under the dependency key: postgresql.auth.password.
Helm vs kubectl apply vs Kustomize¶
| Tool | Strength | Weakness |
|---|---|---|
| kubectl apply | Simple, native | No release history or parameterization |
| Kustomize | Patch-based overlays, built into kubectl | Less packaging ecosystem |
| Helm | Releases, rollback, chart sharing | Template complexity; drift if CLI and GitOps diverge |
Many teams render Helm in CI and commit manifests, or use GitOps operators with native Helm support.
Secrets Management¶
Never commit plaintext production secrets to values.yaml. Options:
- External Secrets Operator syncing from Vault/AWS SM
- Sealed Secrets or SOPS-encrypted values files
- CI-injected
--setfrom secret store at deploy time - Helm secrets plugins (community)
Use helm template or helm install --dry-run=server to validate without persisting.
Hands-on Lab¶
Step 1 – Verify Helm installation¶
Command:
Explanation: Helm 3 communicates directly with Kubernetes API using kubeconfig credentials — no cluster-admin Tiller required.
Expected output:
version.BuildInfo{Version:"v3.x.x", ...}
Kubernetes control plane is running at ...
HELM_NAMESPACE="default"
Step 2 – Create a chart from scratch¶
Command:
mkdir -p ~/helm-lab && cd ~/helm-lab
helm create web-app
tree web-app -L 2 2>/dev/null || find web-app -maxdepth 2 -type f
cat web-app/Chart.yaml
head -20 web-app/values.yaml
Explanation: helm create scaffolds a standard chart with Deployment, Service, Ingress templates, and helpers. Customize rather than writing from zero.
Expected output:
web-app/
├── Chart.yaml
├── values.yaml
├── charts/
└── templates/
├── deployment.yaml
├── service.yaml
...
Step 3 – Customize values and dry-run¶
Command:
cd ~/helm-lab/web-app
cat >> values.yaml <<'EOF'
# Lab overrides
replicaCount: 2
ingress:
enabled: false
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
EOF
helm lint .
helm template web-preview . --namespace helm-lab | head -60
Explanation: helm lint catches syntax errors and best-practice warnings. helm template renders locally without cluster access — ideal for CI and code review.
Expected output:
==> Linting .
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 failure(s)
---
# Source: web-app/templates/service.yaml
apiVersion: v1
kind: Service
...
Step 4 – Install release¶
Command:
helm upgrade --install web-app . \
--namespace helm-lab \
--create-namespace \
--wait --timeout 120s
helm list -n helm-lab
kubectl get deploy,svc,pods -n helm-lab
helm status web-app -n helm-lab
Explanation: --wait blocks until pods are Ready — surfaces probe and scheduling failures immediately.
Expected output:
Step 5 – Upgrade with new values¶
Command:
helm upgrade web-app . \
-n helm-lab \
--set replicaCount=3 \
--set resources.limits.memory=256Mi \
--wait
helm history web-app -n helm-lab
kubectl get deploy web-app -n helm-lab -o jsonpath='{.spec.replicas}{"\n"}'
Explanation: Each upgrade increments revision number. History enables rollback without re-running CI.
Expected output:
Step 6 – Rollback a release¶
Command:
helm rollback web-app 1 -n helm-lab --wait
helm history web-app -n helm-lab
kubectl get deploy web-app -n helm-lab -o jsonpath='{.spec.replicas}{"\n"}'
Explanation: Rollback creates a new revision (3) with spec from revision 1 — not a destructive revert. Audit trail remains intact.
Expected output:
Step 7 – Install from a public repository¶
Command:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami/nginx --versions | head -5
helm upgrade --install demo-nginx bitnami/nginx \
-n helm-lab \
--set replicaCount=1 \
--set service.type=ClusterIP \
--wait --timeout 120s
kubectl get pods -n helm-lab -l app.kubernetes.io/name=nginx
Explanation: Third-party charts accelerate standard software deployment. Always review default values and security settings before production.
Expected output:
Step 8 – Uninstall and clean up¶
Command:
helm uninstall web-app -n helm-lab
helm uninstall demo-nginx -n helm-lab
kubectl delete namespace helm-lab --wait=false
cd ~ && rm -rf ~/helm-lab
helm list -A | grep helm-lab || echo "All helm-lab releases removed"
Explanation: helm uninstall removes release-tracked resources. Resources created outside Helm labels may orphan — use consistent labeling.
Commands & Code¶
| Command | Description | Example |
|---|---|---|
helm create | Scaffold new chart | helm create mychart |
helm lint | Validate chart | helm lint ./mychart |
helm template | Render locally | helm template rel ./mychart -f prod.yaml |
helm upgrade --install | Idempotent deploy | helm upgrade -i app ./chart -n prod |
helm rollback | Revert to revision | helm rollback app 2 -n prod |
helm uninstall | Remove release | helm uninstall app -n prod |
Minimal custom chart values¶
values.yaml for a production web app:
replicaCount: 3
image:
repository: ghcr.io/myorg/web
tag: "v2.4.1"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
hosts:
- host: web.example.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
Environment override values-staging.yaml:
replicaCount: 1
ingress:
hosts:
- host: web.staging.example.com
paths:
- path: /
pathType: Prefix
autoscaling:
enabled: false
Deploy: helm upgrade -i web ./chart -f values.yaml -f values-staging.yaml -n staging
Common Mistakes¶
Pinning chart version but not image tag
helm upgrade bitnami/nginx without --version pulls latest chart — behavior may change. Pin both chart version and image.tag in values.
Storing secrets in values.yaml committed to Git
Plaintext passwords in charts leak via Git history. Use External Secrets, SOPS, or CI-injected overrides.
Editing live manifests outside Helm
kubectl edit on Helm-managed resources causes drift — next upgrade overwrites or conflicts. Change values and upgrade.
Skipping helm lint and dry-run in CI
Template errors discovered in production are expensive. Run helm lint and helm template on every PR.
Best Practices¶
One release name per app per namespace
Convention: <app>-<env> — payments-prod. Document in runbooks for rollback commands.
Use _helpers.tpl for standard labels
Include app.kubernetes.io/name, instance, managed-by: Helm for consistency with ecosystem tools.
SemVer chart versions on every change
Bump Chart.yaml version on template changes — even patch bumps help history correlation.
Integrate with GitOps
Argo CD and Flux support Helm natively. Single source of truth in Git; cluster reconciles continuously.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
Error: rendered manifests contain a resource that already exists | Orphan resource without Helm labels | Adopt with helm upgrade --install and matching labels or delete orphan |
Upgrade hangs on --wait | Pods not Ready | kubectl get pods; fix probes; increase --timeout |
| Wrong config after upgrade | Values precedence | Check -f order; --set overrides last |
helm rollback doesn't fix issue | Bad revision rolled forward | Identify good revision via helm history; rollback to known good |
| Template renders empty | Condition false | Verify {{- if .Values.feature.enabled }} and values |
| Chart not found | Repo not added or stale | helm repo add && helm repo update |
Summary¶
- Helm packages Kubernetes manifests as charts with parameterized values and tracked releases
- helm upgrade --install is the idempotent deploy pattern; history and rollback provide safe iteration
- Templates use Go templating with values, conditionals, and shared partials in
_helpers.tpl - Layer values files per environment; never commit plaintext production secrets
- helm lint, helm template, and --dry-run validate charts before cluster apply
- Helm complements GitOps — charts are the packaging layer; Git remains the source of truth
Interview Questions¶
- What problem does Helm solve compared to raw kubectl apply?
- Explain the difference between a Helm chart, a release, and a revision.
- What changed in Helm 3 compared to Helm 2 regarding cluster security?
- How do you override default values at install time?
- What is the purpose of
_helpers.tplin a chart? - How does
helm rollbackwork — does it delete revision history? - Why should you avoid
kubectl editon Helm-managed resources? - Compare Helm with Kustomize — when would you choose each?
- How do chart dependencies (subcharts) receive configuration from the parent?
- What commands validate a chart before deploying to production?
Sample Answers (Questions 1, 4, and 7)
Q1 — Helm vs kubectl apply: Raw YAML lacks parameterization, release tracking, and rollback. Helm templating parameterizes manifests for multiple environments from one chart. Releases store revision history — upgrade and rollback are first-class operations. Charts publish to repositories for reuse across teams and clusters.
Q4 — Value overrides: Provide -f values.yaml files (later files override earlier), --set key=value for individual overrides, and --set-file for file content. Precedence: chart defaults < -f files left to right < --set. Example: helm upgrade -i app ./chart -f values.yaml -f values-prod.yaml --set replicaCount=5.
Q7 — kubectl edit drift: Helm tracks managed resources via release metadata and labels. Manual edits change live state without updating release values — the next helm upgrade may overwrite edits or fail on three-way merge conflicts. Always change values.yaml and run helm upgrade to maintain consistent desired state.
Related Tutorials¶
- Kubernetes – Category Overview
- Troubleshooting Kubernetes Workloads (previous — Module 5)
- GitOps and CI/CD with Kubernetes (next — Module 6 Production)
- Ingress and External Access
- ConfigMaps and Secrets