Helm Charts — Complete Kubernetes Package Manager Guide 2026
Advertisement
Introduction
Why This Matters
Deploying applications to Kubernetes involves dozens of YAML files — Deployments, Services, ConfigMaps, Ingress rules, and more. Managing these files individually across multiple environments (dev, staging, production) leads to configuration drift, duplication, and manual errors.
Helm solves this by treating your Kubernetes manifests as templates that accept variables. A single Helm chart can deploy identically structured applications across any environment by changing only a values file. In 2026, Helm is the de-facto standard for Kubernetes application packaging, used by projects from NGINX Ingress to cert-manager to Prometheus.
What Is a Helm Chart
A Helm chart is a directory structure containing:
mychart/
Chart.yaml # Chart metadata
values.yaml # Default configuration values
templates/ # Kubernetes manifest templates
deployment.yaml
service.yaml
ingress.yaml
_helpers.tpl # Reusable template snippets
charts/ # Dependency sub-chartsChart.yaml defines the chart identity:
apiVersion: v2
name: mychart
description: A Helm chart for my application
type: application
version: 0.1.0
appVersion: "1.0.0"Helm Templating Syntax
Helm uses Go's text/template engine with Sprig helper functions. Templates interpolate values using double curly braces.
Basic deployment template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
- containerPort: {{ .Values.service.port }}
resources:
{{- toYaml .Values.resources | nindent 12 }}Corresponding values.yaml:
replicaCount: 2
image:
repository: nginx
tag: ""
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
ingress:
enabled: false
host: ""Installing and Managing Releases
Install a chart:
# Install from a local directory
helm install myapp ./mychart
# Install with custom values file
helm install myapp ./mychart -f values.production.yaml
# Install overriding specific values inline
helm install myapp ./mychart \
--set image.tag=v2.1.0 \
--set replicaCount=3
# Install into a specific namespace
helm install myapp ./mychart -n production --create-namespaceUpgrade and rollback:
# Upgrade a release
helm upgrade myapp ./mychart -f values.production.yaml
# Atomic upgrade — auto-rollback on failure
helm upgrade myapp ./mychart --atomic --timeout 5m
# View release history
helm history myapp
# Rollback to a specific revision
helm rollback myapp 1Inspect and debug:
helm list -A # All releases across namespaces
helm get values myapp # See computed values
helm get manifest myapp # See rendered manifests
helm template myapp ./mychart # Render locally without installing
helm status myapp # Release status summaryWorking With Chart Repositories
# Add popular repos
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add cert-manager https://charts.jetstack.io
# Update repo cache
helm repo update
# Search for a chart
helm search repo nginx
# Inspect default values before installing
helm show values ingress-nginx/ingress-nginx
# Install NGINX Ingress Controller
helm install ingress ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.type=LoadBalancerHelm Hooks and Tests
Hooks let you run Jobs at specific points in the release lifecycle — ideal for database migrations.
# templates/db-migrate.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["python", "manage.py", "migrate"]Helm test to verify a deployment:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test"
annotations:
"helm.sh/hook": test
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}']Run with helm test myapp after installation.
Packaging and Publishing Charts
# Lint a chart for syntax and logic errors
helm lint ./mychart
# Package into a versioned .tgz archive
helm package ./mychart
# Push to an OCI registry (Helm 3.8+)
helm push mychart-0.1.0.tgz oci://ghcr.io/myorg/charts
# Install from OCI registry
helm install myapp oci://ghcr.io/myorg/charts/mychart --version 0.1.0Common Mistakes
- Hardcoding image tags — use
{{ .Values.image.tag | default .Chart.AppVersion }}so upgrades remain declarative. - Skipping
helm lint— run it in CI before merging chart changes; it catches template syntax errors early. - Not using
--atomic— without it, a failed upgrade leaves a broken release;--atomictriggers automatic rollback. - Ignoring hook weights — when multiple hooks fire at the same lifecycle point, undefined order causes race conditions.
- Storing secrets in
values.yaml— use Kubernetes Secrets, external-secrets operator, or the Helm Secrets plugin instead. - Forgetting
helm repo update— stale indexes cause "chart not found" errors during installs.
Best Practices
- One chart per application — avoid mega-charts; model shared components (Redis, PostgreSQL) as sub-chart dependencies.
- Version your charts semantically — bump
versioninChart.yamlon every chart change;appVersiontracks the container image version. - Use named templates in
_helpers.tpl— keeplabels,selectorLabels, andfullnamehelpers DRY across all templates. - Environment-specific values files — maintain
values.dev.yaml,values.staging.yaml,values.production.yaml; never mutate the basevalues.yamlper environment. - Preview with
helm template— render manifests locally beforehelm installto catch issues without touching the cluster. - Adopt OCI registries — push charts to GitHub Container Registry or AWS ECR; OCI support is stable in Helm 3.8+.
Key Takeaways
- Helm is the Kubernetes package manager that templatizes YAML manifests and manages versioned releases across environments.
- A chart is a versioned directory containing
Chart.yaml,values.yaml, and atemplates/folder with Go template files. helm install,helm upgrade, andhelm rollbackmanage the full lifecycle of a Helm release.- Helm hooks run Kubernetes Jobs at specific lifecycle points (
pre-install,pre-upgrade,post-upgrade) — ideal for database migrations. - The
--atomicflag onhelm upgradeautomatically rolls back to the previous working release if the upgrade fails. - Chart repositories can be traditional HTTP indexes or modern OCI registries; OCI support has been stable since Helm 3.8.
- Secrets must never be stored in
values.yaml; use Kubernetes Secrets, Vault, or the Helm Secrets plugin with encryption. - Always run
helm lintandhelm templatein CI pipelines before deploying chart changes to any environment.
Advertisement