Helm Charts: Kubernetes Package Management
Kubernetes deployments with Helm package management
Helm has revolutionized Kubernetes application deployment by introducing package management concepts familiar from traditional operating systems.
As Kubernetes adoption grows, managing complex applications with dozens of YAML files becomes challenging. Helm Charts solve this problem by bundling all resources into versioned, configurable packages.
This nice image is generated by AI model Flux 1 dev.
Understanding Helm: The Kubernetes Package Manager
Helm is to Kubernetes what apt is to Debian, yum to RedHat, or Homebrew to macOS. It packages Kubernetes applications into Charts – collections of files that describe related Kubernetes resources. A single Chart might deploy a complete application stack: web servers, databases, caching layers, ingress rules, and monitoring components. For those new to Kubernetes, a Kubernetes Cheatsheet provides essential commands and concepts to get started.
Why Helm Matters in Modern DevOps
Complexity Reduction: Instead of managing 20+ YAML files, you manage one Chart with customizable values.
Reproducibility: Deploy identical configurations across development, staging, and production with environment-specific value overrides. This is especially valuable when deploying complex microservices architectures where consistency matters.
Version Control: Charts are versioned, enabling easy rollbacks and upgrade tracking.
Community Ecosystem: Thousands of pre-built Charts available through Artifact Hub (previously Helm Hub) for popular applications like PostgreSQL, Redis, NGINX, Prometheus, and more.
Templating Power: Go templates enable dynamic resource generation based on input values, reducing duplication.
Helm Architecture and Core Concepts
Helm 3 Architecture
Helm 3 simplified the architecture by removing Tiller, the problematic server-side component from Helm 2:
- Helm Client: CLI tool that interacts directly with Kubernetes API
- Chart: Package format containing templates and metadata
- Release: An instance of a Chart running in a Kubernetes cluster
- Repository: Storage location for Charts (HTTP server or OCI registry)
Key Components of a Helm Chart
my-app-chart/
├── Chart.yaml # Chart metadata and version
├── values.yaml # Default configuration values
├── charts/ # Dependent charts
├── templates/ # Kubernetes resource templates
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl # Template helpers
│ └── NOTES.txt # Post-installation notes
├── .helmignore # Files to ignore when packaging
├── README.md
└── LICENSE
Creating Your First Helm Chart
Initialize a New Chart
helm create my-application
cd my-application
This generates a starter Chart with example templates for a deployment, service, and ingress.
Chart.yaml: Defining Metadata
apiVersion: v2
name: my-application
description: A production-ready application chart
type: application
version: 1.0.0 # Chart version
appVersion: "2.4.1" # Version of the application
maintainers:
- name: Your Team
email: team@company.com
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
Values.yaml: Configuration Management
The values.yaml file defines default configurations that users can override. This approach separates configuration from templates, making it easy to manage different environments (development, staging, production) and customize deployments without modifying template files.
replicaCount: 3
image:
repository: myapp/backend
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 80
Templates: Dynamic Kubernetes Manifests
Templates use Go templating syntax to generate Kubernetes resources dynamically. These templates can generate any Kubernetes resource type, from simple Deployments to complex StatefulSets for stateful applications requiring persistent storage. For applications that need stable identities and persistent volumes, you’ll want to use StatefulSets instead of Deployments, as detailed in our guide on StatefulSets and Persistent Storage in Kubernetes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-application.fullname" . }}
labels:
{{- include "my-application.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-application.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-application.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 8080
protocol: TCP
resources:
{{- toYaml .Values.resources | nindent 10 }}
Template Helpers (_helpers.tpl)
Create reusable template functions to avoid repetition:
{{/*
Expand the name of the chart.
*/}}
{{- define "my-application.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "my-application.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "my-application.labels" -}}
helm.sh/chart: {{ include "my-application.chart" . }}
{{ include "my-application.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
Managing Helm Charts: Installation and Operations
Installing a Chart
# Install from a repository
helm install my-release bitnami/postgresql
# Install from local directory
helm install my-app ./my-application
# Install with custom values
helm install my-app ./my-application -f values-production.yaml
# Install with inline value overrides
helm install my-app ./my-application \
--set replicaCount=5 \
--set image.tag=2.0.0
Upgrading Releases
# Upgrade with new values
helm upgrade my-app ./my-application -f values-production.yaml
# Upgrade with atomic rollback on failure
helm upgrade my-app ./my-application --atomic --timeout 5m
# Force resource updates
helm upgrade my-app ./my-application --force
Rollback and History
# View release history
helm history my-app
# Rollback to previous version
helm rollback my-app
# Rollback to specific revision
helm rollback my-app 3
Testing and Debugging
# Dry-run to see generated manifests
helm install my-app ./my-application --dry-run --debug
# Template rendering without installation
helm template my-app ./my-application
# Lint chart for issues
helm lint ./my-application
# Test release with test hooks
helm test my-app
Advanced Helm Features
Chart Dependencies
Helm Charts can depend on other Charts, allowing you to compose complex applications from reusable components. This is particularly useful when deploying microservices that need databases, message queues, or other supporting services. Define dependencies in Chart.yaml:
dependencies:
- name: redis
version: "17.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
Update dependencies:
helm dependency update ./my-application
Helm Hooks for Lifecycle Management
Hooks execute at specific points in the release lifecycle:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-application.fullname" . }}-db-migration
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
containers:
- name: db-migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["python", "manage.py", "migrate"]
restartPolicy: Never
Hook types include:
pre-install: Before resources are installedpost-install: After all resources are installedpre-upgrade: Before upgradepost-upgrade: After upgradepre-delete: Before deletionpost-delete: After deletionpre-rollback: Before rollbackpost-rollback: After rollback
Conditional Logic and Flow Control
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-application.fullname" . }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "my-application.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
OCI Registry Support: Modern Chart Distribution
Since Helm 3.8, OCI (Open Container Initiative) registry support is stable, allowing Charts to be stored alongside container images.
Publishing to OCI Registry
# Login to registry
helm registry login registry.example.com
# Package the chart
helm package ./my-application
# Push to OCI registry
helm push my-application-1.0.0.tgz oci://registry.example.com/charts
# Install from OCI registry
helm install my-app oci://registry.example.com/charts/my-application --version 1.0.0
Benefits of OCI Registries
- Unified Storage: Charts and images in one place
- Standard Tooling: Use existing registry infrastructure
- Better Security: Leverage registry authentication and scanning
- Simpler Management: No separate Chart repository server needed
Best Practices for Production Helm Charts
1. Values Structure and Documentation
Document all values with comments:
# -- Number of replicas for the application
replicaCount: 3
# -- Image configuration
image:
# -- Image repository
repository: myapp/backend
# -- Image pull policy
pullPolicy: IfNotPresent
# -- Image tag (defaults to chart appVersion)
tag: ""
2. Resource Management
Always set resource requests and limits:
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
3. Security Contexts
Define security contexts for containers:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
4. Health Checks
Include liveness and readiness probes:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
5. ConfigMap and Secret Management
Proper secret management is critical for production deployments. Use external secret managers (Sealed Secrets, External Secrets Operator, or Vault) rather than storing secrets in values files. This ensures sensitive data like database passwords, API keys, and certificates are handled securely:
envFrom:
- secretRef:
name: {{ include "my-application.fullname" . }}-secrets
- configMapRef:
name: {{ include "my-application.fullname" . }}-config
6. Schema Validation
Create values.schema.json to validate user inputs:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"required": ["repository"]
}
},
"required": ["image"]
}
7. NOTES.txt for User Guidance
Provide post-installation instructions:
1. Get the application URL by running:
{{- if .Values.ingress.enabled }}
https://{{ (index .Values.ingress.hosts 0).host }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "my-application.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- end }}
2. Monitor the deployment:
kubectl --namespace {{ .Release.Namespace }} get pods -l "app.kubernetes.io/name={{ include "my-application.name" . }}"
Helm Chart Testing and CI/CD Integration
Chart Testing with chart-testing (ct)
# Install chart-testing
brew install chart-testing
# Lint charts
ct lint --config ct.yaml
# Install and test charts
ct install --config ct.yaml
GitHub Actions Example
name: Helm Chart CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v3
with:
version: 3.12.0
- name: Set up chart-testing
uses: helm/chart-testing-action@v2
- name: Lint charts
run: ct lint --config ct.yaml
- name: Create kind cluster
uses: helm/kind-action@v1
- name: Install charts
run: ct install --config ct.yaml
GitOps with Helm: ArgoCD and Flux
GitOps tools like ArgoCD and Flux integrate seamlessly with Helm Charts, enabling declarative, automated deployments. These tools watch your Git repository for changes and automatically sync Helm releases, making continuous deployment straightforward. For complex microservices architectures, consider how distributed transaction patterns like the Saga pattern can help manage consistency across services deployed via Helm.
ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-application
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/my-app-chart
targetRevision: main
path: charts/my-application
helm:
valueFiles:
- values-production.yaml
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
Flux HelmRelease
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: my-application
namespace: flux-system
spec:
interval: 5m
chart:
spec:
chart: my-application
version: '1.x.x'
sourceRef:
kind: HelmRepository
name: my-charts
values:
replicaCount: 5
image:
tag: "2.0.0"
Troubleshooting Common Helm Issues
Issue: Failed Upgrade Stuck in Pending
# Check release status
helm list --all-namespaces
# Get release details
helm status my-app -n namespace
# Force delete if necessary (use carefully)
kubectl delete secret -n namespace -l owner=helm,name=my-app
Issue: Template Rendering Errors
# Debug template rendering
helm template my-app ./my-application --debug
# Validate against Kubernetes
helm template my-app ./my-application | kubectl apply --dry-run=client -f -
Issue: Values Not Applied
# Check merged values
helm get values my-app
# Show all computed values
helm get values my-app --all
Helm Ecosystem and Tools
The Helm ecosystem includes numerous tools that extend its capabilities and integrate with other Kubernetes technologies. When deploying applications that require advanced networking features like service-to-service communication, traffic management, and security policies, consider integrating with a Service Mesh with Istio and Linkerd, which can be deployed and managed through Helm Charts.
Essential Tools
- Helmfile: Declarative spec for deploying Helm Charts
- Helm Diff: Preview changes before upgrade
- Helm Secrets: Manage secrets with SOPS
- Nova: Find outdated Helm Charts
- Pluto: Detect deprecated Kubernetes APIs
Example Helmfile
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
releases:
- name: postgresql
namespace: database
chart: bitnami/postgresql
version: 12.x.x
values:
- postgresql:
auth:
database: myapp
username: appuser
- name: my-app
namespace: production
chart: ./charts/my-application
values:
- values-production.yaml
needs:
- database/postgresql
The Future of Helm
The Helm ecosystem continues evolving:
- OCI-Native: Full transition to OCI registries as the standard
- Improved Security: Better secret management and signing capabilities
- Performance: Faster rendering and installation for large Charts
- WASM Support: WebAssembly for Chart plugins and extensions
- Better Validation: Enhanced schema validation and policy enforcement
Conclusion
Helm has become the de facto standard for Kubernetes package management, and mastering it is essential for modern DevOps practices. By understanding Chart structure, templating, values management, and best practices, you can create maintainable, secure, and scalable Kubernetes deployments.
Start with simple Charts, gradually incorporate advanced features like hooks and dependencies, and integrate with GitOps tools for production-grade infrastructure. The Helm community provides thousands of pre-built Charts, but the real power comes from creating custom Charts tailored to your organization’s needs. Whether you’re deploying stateless applications or stateful workloads requiring persistent storage, Helm simplifies the complexity of Kubernetes resource management. For teams setting up new Kubernetes clusters, consider installing Kubernetes with Kubespray or exploring Kubernetes distributions like k3s or MicroK8s for development environments. If you’re evaluating which distribution fits your homelab or small cluster needs, see our comprehensive comparison of Kubernetes distributions for detailed analysis.
Useful Links
- Official Helm Documentation
- Artifact Hub - Discover Helm Charts
- Helm GitHub Repository
- Helm Best Practices Guide
- Chart Testing Tool
- Helmfile
- ArgoCD
- Flux CD
- Bitnami Charts Repository
- Kubernetes Cheatsheet
- Install Kubernetes with Kubespray
- Comparison of Kubernetes Distributions for a 3-Node Homelab
- Kubernetes distributions - quick overview of kubeadm, k3s, MicroK8s, Minikube, Talos Linux and RKE2
- Service Mesh with Istio and Linkerd
- StatefulSets and Persistent Storage in Kubernetes
- Saga Pattern in Distributed Transactions