Helm Charts
A comprehensive guide to Helm Charts covering package management, templating, chart development, repository management, versioning, and best practices for packaging Kubernetes applications.
Helm is the package manager for Kubernetes. It helps you define, install, and upgrade complex Kubernetes applications. Helm charts are packages of pre-configured Kubernetes resources that can be versioned, shared, and reused.
Helm solves the challenge of deploying complex microservices applications that consist of multiple interdependent Kubernetes resources. Instead of managing dozens of YAML files manually, you can use a single chart with configurable parameters.
- Chart: A package of Kubernetes resources (templates + metadata)
- Release: An instance of a chart running in a Kubernetes cluster
- Repository: A collection of packaged charts (like a registry for charts)
- Values: Configuration parameters that customize chart deployment
- Template: A file that defines Kubernetes resources using Go templates
Helm consists of two main components: the Helm client (CLI) and the Tiller server (deprecated in Helm v3). In Helm v3, Tiller was removed, making Helm more secure and simpler.
Helm CLI
Chart Storage
Render Engine
Chart Repository
# Install Helm v3
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
# Verify installation
helm version
# Add Helm repositories
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Search for charts
helm search repo nginx
helm search repo --versions nginx
# Install a chart
helm install my-nginx bitnami/nginx --namespace default
# List releases
helm list
helm list --all-namespaces
# Uninstall a release
helm uninstall my-nginx
A Helm chart follows a specific directory structure. Understanding this structure is essential for chart development.
# Chart Directory Structure
my-app/
├── Chart.yaml # Chart metadata (name, version, description)
├── values.yaml # Default configuration values
├── values-*.yaml # Environment-specific values (optional)
├── charts/ # Dependent charts (subcharts)
├── crds/ # Custom Resource Definitions
├── templates/ # Kubernetes resource templates
│ ├── _helpers.tpl # Template helper functions
│ ├── deployment.yaml # Deployment manifest template
│ ├── service.yaml # Service manifest template
│ ├── ingress.yaml # Ingress manifest template
│ ├── configmap.yaml # ConfigMap template
│ ├── secret.yaml # Secret template (not recommended)
│ ├── hpa.yaml # HorizontalPodAutoscaler template
│ ├── NOTES.txt # Post-installation notes
│ └── tests/ # Test resources
└── README.md # Documentation
# Chart.yaml Example
apiVersion: v2
name: my-app
description: A Helm chart for my application
type: application
version: 1.2.3 # Chart version (semver)
appVersion: 2.5.0 # Application version
maintainers:
- name: John Doe
email: john@example.com
- name: Jane Doe
email: jane@example.com
home: https://example.com
icon: https://example.com/icon.png
keywords:
- web
- microservice
sources:
- https://github.com/example/my-app
dependencies:
- name: postgresql
version: 11.x.x
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
appVersion field indicates the version of the application being packaged, which may differ from the chart version.
Helm uses Go templates with additional functions and pipelines to generate Kubernetes manifests dynamically. This enables configuration-driven deployments with maximum flexibility.
Template Directives
{{ }} for actions, {{ . }} for the root object, {{ .Values }} for values, and {{ .Release }} for release metadata.Template Functions
include, default, required, toYaml, fromYaml, and tpl for text processing.Sprig Functions
Security Context
# _helpers.tpl - Template functions
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.name" . }}-{{ .Chart.Version | replace "+" "_" }}
{{ include "my-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
env:
- name: ENVIRONMENT
value: {{ .Values.environment | default "production" | quote }}
{{- with .Values.env }}
{{- toYaml . | nindent 8 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 10 }}
{{- end }}
# Advanced Templating Examples
# Conditional blocks
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-app.fullname" . }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.className }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "my-app.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
# Range loops
{{- range $key, $value := .Values.configMap }}
{{ $key }}: {{ $value | quote }}
{{- end }}
# with statement for optional fields
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}
# Required values
{{- required "A valid .Values.service.type is required" .Values.service.type }}
# Default values
{{- .Values.image.pullPolicy | default "IfNotPresent" }}
# Include and indent
{{ include "my-app.labels" . | indent 4 }}
# Sprig functions
{{- .Values.image.repository | default "nginx" | quote }}
{{- .Values.replicaCount | int | add 1 }}
{{- .Values.env | toYaml | nindent 4 }}
Helm values provide a powerful way to configure chart behavior across different environments. Understanding values management is essential for production deployments.
# values.yaml (default configuration)
replicaCount: 1
image:
repository: nginx
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: false
className: ""
annotations: {}
hosts: []
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
environment: production
configMap:
key1: value1
key2: value2
secrets:
key1: base64-encoded-value
env:
- name: LOG_LEVEL
value: info
# values-dev.yaml (development override)
replicaCount: 1
image:
tag: dev
environment: development
ingress:
enabled: true
hosts:
- host: dev.example.com
paths:
- path: /
pathType: Prefix
# values-prod.yaml (production override)
replicaCount: 3
image:
tag: latest
environment: production
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "1000m"
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
# Install with values
helm install my-app ./my-app -f values-dev.yaml
helm upgrade my-app ./my-app -f values-prod.yaml
# Set individual values
helm install my-app ./my-app --set image.tag=2.0.0 --set replicaCount=5
# Use multiple values files
helm install my-app ./my-app -f values-base.yaml -f values-prod.yaml
- Use environment-specific values files (values-dev.yaml, values-prod.yaml)
- Keep default values in values.yaml (apply to all environments)
- Use
--setfor overriding individual values during CI/CD - Validate values with schema validation (values.schema.json)
- Document all configurable values in README.md
Developing Helm charts requires a systematic workflow for creation, testing, and validation.
# Create a new chart
helm create my-chart
# Dry run to preview rendered manifests
helm install my-release ./my-chart --dry-run --debug
# Render templates only
helm template my-chart ./my-chart --debug
# Lint chart for best practices
helm lint ./my-chart
# Test chart installation in a namespace
kubectl create namespace test
helm install test-release ./my-chart --namespace test --set environment=test
# Run chart tests (if defined in templates/tests/)
helm test test-release
# Package chart for distribution
helm package ./my-chart
# Check chart dependencies
helm dependency list ./my-chart
helm dependency update ./my-chart
# Unit tests for templates
# Using helm-unittest plugin
helm plugin install https://github.com/helm-unittest/helm-unittest
helm unittest ./my-chart
# Example test file (templates/tests/deployment-test.yaml)
---
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test-connection"
labels:
{{- include "my-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
- Always run
helm lintbefore packaging - Use
helm install --dry-run --debugto validate templates - Write unit tests for critical templates
- Use
helm testfor integration testing - Version charts using semantic versioning
- Keep charts small and focused (single responsibility)
- Use dependency management for complex applications
Helm repositories store and distribute charts. They can be hosted on HTTP servers, cloud storage, or OCI registries.
# Add a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# List repositories
helm repo list
# Update repository index
helm repo update
# Search repositories
helm search repo bitnami/nginx
helm search repo --versions nginx
# Remove a repository
helm repo remove bitnami
# Package and push to repository (using ChartMuseum)
helm package ./my-chart
curl --data-binary "@my-chart-1.2.3.tgz" http://chartmuseum-server/api/charts
# OCI Registry (Helm v3.7+)
helm registry login registry.example.com
helm push my-chart-1.2.3.tgz oci://registry.example.com/helm-charts
# Install from OCI registry
helm install my-release oci://registry.example.com/helm-charts/my-chart --version 1.2.3
# Create a repository index
helm repo index ./my-repo --url https://example.com/charts
# Using GitHub Pages as a Helm repo
# Create gh-pages branch
# Package and index charts
# Upload to gh-pages branch
# Access via https://username.github.io/repo-name/
Chart Packaging
Templating
include instead of template for better scoping.Security
Versioning
Values Organization
Testing
helm unittest for unit testing, helm test for integration testing, and helm lint for validation.# values.schema.json - JSON Schema Validation
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 10
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"tag": {
"type": "string"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
},
"required": ["repository", "tag"]
},
"service": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
},
"required": ["type", "port"]
},
"resources": {
"type": "object",
"properties": {
"requests": {
"type": "object",
"properties": {
"memory": {
"type": "string"
},
"cpu": {
"type": "string"
}
}
},
"limits": {
"type": "object",
"properties": {
"memory": {
"type": "string"
},
"cpu": {
"type": "string"
}
}
}
}
}
},
"required": ["image", "service"]
}
helm history <release-name> to see revision history, then helm rollback <release-name> <revision> to rollback. Helm stores release history in Kubernetes Secrets.helm install --dry-run --debug to see rendered manifests with debugging info. Use helm template --debug for template-only rendering. Use --set to test different value combinations.--set for CI/CD overrides. Use Helm charts with ArgoCD or Flux for GitOps-based environment management.Helm is the industry standard for Kubernetes package management. Master Helm charts to streamline your Kubernetes deployments, reduce complexity, and enable reusable infrastructure patterns.