Introduction — The YAML Problem
If you completed Part 3 on Kubernetes Fundamentals, you know deploying an application requires writing Deployments, Services, ConfigMaps, Ingresses, and more — each as a separate YAML manifest. Now imagine maintaining 15 microservices across development, staging, and production. You're staring at hundreds of nearly-identical YAML files with only a handful of values changing between environments.
This is the YAML problem: Kubernetes manifests are verbose, repetitive, and hard to manage at scale. You need a way to templatize, version, and distribute your Kubernetes configurations.
What is Helm?
Helm is the package manager for Kubernetes. If apt is to Ubuntu what brew is to macOS, then Helm is the equivalent for Kubernetes clusters. It introduces three core concepts:
- Chart — A package containing all resource definitions needed to run an application in Kubernetes. Think of it as a versioned archive of templated YAML.
- Release — A running instance of a chart deployed into a cluster. You can have multiple releases of the same chart with different configurations.
- Repository — A collection of charts available for installation, analogous to a package registry like npm or PyPI.
flowchart LR
A[Chart Repository] -->|helm install| B[Helm Client]
B -->|Render Templates| C[Kubernetes API]
C --> D[Release in Cluster]
D -->|helm upgrade| C
D -->|helm rollback| C
E[values.yaml] -->|Override| B
F[--set flags] -->|Override| B
Helm 3 (the current major version) operates as a client-side tool — there is no server-side component like the deprecated Tiller from Helm 2. This simplifies security and RBAC significantly.
Installing Helm
Helm supports multiple installation methods across all major operating systems. Choose the one appropriate for your environment:
# macOS (Homebrew)
brew install helm
# Windows (Chocolatey)
choco install kubernetes-helm
# Windows (Scoop)
scoop install helm
# Linux (snap)
sudo snap install helm --classic
# Linux (script - recommended for CI/CD)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Verify installation
helm version
# version.BuildInfo{Version:"v3.15.x", ...}
Once installed, verify Helm can communicate with your cluster by running helm list — this should return an empty table if no releases are deployed.
Helm Chart Structure
A Helm chart is a directory with a specific layout. Understanding this structure is essential before creating or consuming charts:
flowchart TD
A[my-app/] --> B[Chart.yaml]
A --> C[values.yaml]
A --> D[templates/]
A --> E[charts/]
A --> F[.helmignore]
D --> G[deployment.yaml]
D --> H[service.yaml]
D --> I[ingress.yaml]
D --> J[_helpers.tpl]
D --> K[NOTES.txt]
E --> L[subchart/]
B -.->|"name, version, appVersion"| B
C -.->|"default config values"| C
J -.->|"reusable template snippets"| J
Let's examine each critical file:
# Chart.yaml - Chart metadata
apiVersion: v2
name: my-web-app
description: A production-ready web application chart
type: application
version: 1.2.0 # Chart version (SemVer)
appVersion: "3.4.1" # Application version
keywords:
- web
- nodejs
- microservice
maintainers:
- name: Wasil Zafar
email: wasil.zafar@gmail.com
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
version tracks the chart's packaging version (bump when templates change). appVersion tracks the actual application version (bump when your app code changes). They evolve independently.
# values.yaml - Default configuration
replicaCount: 2
image:
repository: myregistry.azurecr.io/my-web-app
tag: "3.4.1"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 3000
ingress:
enabled: true
className: nginx
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilization: 70
Template Functions & Pipelines
Helm templates use the Go template language with Sprig library extensions. This gives you access to over 100 functions for string manipulation, math, encoding, and flow control.
Core Template Syntax
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-web-app.fullname" . }}
labels:
{{- include "my-web-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-web-app.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "my-web-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
Key Template Concepts
- Pipelines — Chain functions with
|:{{ .Values.name | upper | quote }} - Flow Control —
if/else,range,withblocks for conditional and iterative rendering - Named Templates — Defined in
_helpers.tplwith{{- define "chart.name" -}}and invoked withinclude - Whitespace Control — Use
{{-and-}}to trim leading/trailing whitespace - nindent — Indents output by N spaces, critical for valid YAML nesting
Template Debugging with helm template
Before deploying, always render your templates locally to catch errors. Run helm template my-release ./my-web-app --values values-staging.yaml to see the fully rendered YAML without contacting the cluster. Pipe through kubectl apply --dry-run=client -f - for additional validation.
Creating a Helm Chart
The fastest way to start is with Helm's scaffolding command, then customise the generated files:
# Scaffold a new chart
helm create my-web-app
# Explore generated structure
tree my-web-app/
# my-web-app/
# ├── .helmignore
# ├── Chart.yaml
# ├── values.yaml
# ├── charts/
# └── templates/
# ├── NOTES.txt
# ├── _helpers.tpl
# ├── deployment.yaml
# ├── hpa.yaml
# ├── ingress.yaml
# ├── service.yaml
# ├── serviceaccount.yaml
# └── tests/
# └── test-connection.yaml
# Lint the chart for errors
helm lint ./my-web-app
# Package the chart as a .tgz archive
helm package ./my-web-app
# Successfully packaged chart and saved it to: my-web-app-1.2.0.tgz
The scaffold provides sensible defaults including a Deployment, Service, Ingress, HPA, and ServiceAccount — all templated with best-practice patterns. Strip out what you don't need, customise what you do.
Deploying with Helm
Helm's release lifecycle revolves around four primary commands: install, upgrade, rollback, and uninstall.
# Install a chart (creates a new release)
helm install my-release ./my-web-app \
--namespace production \
--create-namespace \
--values values-production.yaml
# Check release status
helm status my-release -n production
# List all releases
helm list --all-namespaces
# Upgrade a release (apply changes)
helm upgrade my-release ./my-web-app \
--namespace production \
--values values-production.yaml \
--set image.tag="3.5.0"
# View release history
helm history my-release -n production
# REVISION STATUS DESCRIPTION
# 1 superseded Install complete
# 2 deployed Upgrade complete
# Rollback to a previous revision
helm rollback my-release 1 -n production
# Uninstall a release (removes all resources)
helm uninstall my-release -n production
helm upgrade --install (the "upsert" pattern) in CI/CD pipelines. This creates the release if it doesn't exist or upgrades it if it does — making your pipeline idempotent.
Values & Overrides
Helm's value system provides a layered configuration approach. Values are merged in a specific precedence order (highest wins):
--set/--set-stringflags (highest precedence)-f/--valuesfiles (in order, last file wins)- Parent chart's values for subcharts
- Chart's default
values.yaml(lowest precedence)
# values-production.yaml — environment-specific overrides
replicaCount: 5
image:
tag: "3.5.0"
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: app.production.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.production.example.com
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 5
maxReplicas: 20
targetCPUUtilization: 65
This layered approach lets you maintain a single chart with environment-specific values files — values-dev.yaml, values-staging.yaml, values-production.yaml — rather than duplicating entire manifests.
Helm Repositories
Helm repositories are HTTP servers hosting an index.yaml file that references packaged charts. The community has standardised on OCI registries as the modern distribution mechanism.
# Add a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
# Update repository cache
helm repo update
# Search for charts
helm search repo nginx
helm search hub prometheus # Search Artifact Hub
# Install from a repository
helm install my-nginx ingress-nginx/ingress-nginx \
--namespace ingress \
--create-namespace \
--version 4.10.0
# Pull a chart locally for inspection
helm pull bitnami/postgresql --version 12.5.6 --untar
# OCI-based registry (modern approach)
helm push my-web-app-1.2.0.tgz oci://myregistry.azurecr.io/helm
helm install my-release oci://myregistry.azurecr.io/helm/my-web-app --version 1.2.0
--version 4.10.0). Never use floating versions like latest — an upstream chart update could break your deployments without warning.
Helm Best Practices
Production-grade Helm usage requires discipline around versioning, testing, and dependency management:
Chart Versioning
- Follow Semantic Versioning strictly — MAJOR for breaking changes, MINOR for features, PATCH for fixes
- Bump
versionin Chart.yaml on every template change - Keep
appVersionin sync with your container image tag - Store charts in version control alongside application code (monorepo) or a dedicated charts repository
Dependencies
- Declare sub-charts in
Chart.yamlunderdependencies - Run
helm dependency updateto download and lock versions - Use
conditionfields to make dependencies optional - Pin dependency versions — never use wildcards in production
Hooks & Testing
- Hooks — Execute jobs at specific lifecycle points (pre-install, post-upgrade, pre-delete) for migrations, backups, or notifications
- helm test — Run test pods defined in
templates/tests/to validate a release is functioning correctly - helm lint — Static analysis for template errors before packaging
- helm template — Render templates locally for review and CI validation
CI/CD Pipeline Integration
A robust Helm CI/CD pipeline: (1) helm lint on every PR, (2) helm template + kubectl apply --dry-run=server for validation, (3) helm package + push to OCI registry on merge to main, (4) helm upgrade --install --atomic --timeout 5m for deployment with automatic rollback on failure.
Preparing for ArgoCD
Helm charts are first-class citizens in GitOps workflows. ArgoCD (which we'll cover in Part 6) natively supports Helm as a configuration source. Here's how Helm fits into the GitOps paradigm:
- Chart Source — ArgoCD can pull charts from Git repos or Helm repositories directly
- Values Management — ArgoCD's Application spec supports inline values, values files from Git, or Helm parameter overrides
- Declarative Releases — Instead of imperative
helm install, ArgoCD reconciles desired state continuously - Drift Detection — ArgoCD detects when live state diverges from the chart's rendered output and can auto-sync
helm install manually. Instead, you commit chart changes and values files to Git — the GitOps controller (ArgoCD/Flux) handles deployment automatically. This eliminates "it worked on my machine" deployment issues.
The mental shift: Helm becomes your packaging and templating tool, while ArgoCD becomes your deployment operator. You still write and maintain Helm charts, but the deployment lifecycle is managed declaratively through Git commits.
Conclusion & What's Next
Helm transforms Kubernetes manifest management from a tedious, error-prone copy-paste exercise into a structured, versioned, and distributable workflow. You've learned to:
- Understand Helm's core concepts — charts, releases, and repositories
- Create and structure charts with proper templating using Go templates
- Deploy, upgrade, and rollback releases with confidence
- Manage environment-specific configurations through layered values
- Follow production best practices for versioning, testing, and security
Next in the Series
In Part 5: GitOps Foundations & Architecture, we'll explore the GitOps methodology — declarative infrastructure, Git as the single source of truth, reconciliation loops, and how tools like ArgoCD and Flux implement these principles at scale.