Kubernetes Helm Charts: Packaging Applications
devops kubernetes helm
Raw Kubernetes YAML gets repetitive. Helm packages applications into reusable charts with templating and configuration management.
What is Helm?
Helm is:
- Package manager for Kubernetes
- Templating engine for manifests
- Release management tool
Think apt/yum/npm for Kubernetes.
Installation
# macOS
brew install helm
# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
Using Existing Charts
Add Repositories
# Add official stable repo
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
Search Charts
helm search repo postgresql
helm search hub postgresql # Search Artifact Hub
Install a Chart
# Basic install
helm install my-postgres bitnami/postgresql
# With custom values
helm install my-postgres bitnami/postgresql -f values.yaml
# Or inline values
helm install my-postgres bitnami/postgresql --set auth.postgresPassword=secret
Manage Releases
# List releases
helm list
# Check status
helm status my-postgres
# Upgrade
helm upgrade my-postgres bitnami/postgresql --set replicas=3
# Rollback
helm rollback my-postgres 1
# Uninstall
helm uninstall my-postgres
Creating Your Own Chart
Scaffold
helm create my-app
Creates:
my-app/
├── Chart.yaml # Metadata
├── values.yaml # Default configuration
├── templates/ # Kubernetes manifests with templating
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── _helpers.tpl # Template helpers
└── charts/ # Dependencies
Chart.yaml
apiVersion: v2
name: my-app
description: My application Helm chart
type: application
version: 0.1.0
appVersion: "1.0.0"
values.yaml
replicaCount: 1
image:
repository: myapp
tag: "latest"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: false
hostname: myapp.example.com
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
Template Example
# templates/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 }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 80
resources:
{{- toYaml .Values.resources | nindent 12 }}
Template Functions
# String functions
{{ .Values.name | upper }}
{{ .Values.name | quote }}
{{ .Values.name | default "default-value" }}
# Conditionals
{{- if .Values.ingress.enabled }}
# Ingress config here
{{- end }}
# Loops
{{- range .Values.extraEnv }}
- name: {{ .name }}
value: {{ .value }}
{{- end }}
# Include other templates
{{ include "my-app.fullname" . }}
_helpers.tpl
{{/*
Create a default fully qualified app name.
*/}}
{{- define "my-app.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 }}
Testing Charts
Lint
helm lint my-app
Template Preview
# See generated YAML
helm template my-app ./my-app
# With custom values
helm template my-app ./my-app -f production-values.yaml
Dry Run
helm install my-app ./my-app --dry-run --debug
Chart Dependencies
# Chart.yaml
dependencies:
- name: postgresql
version: "11.x.x"
repository: "https://charts.bitnami.com/bitnami"
helm dependency update
Versioning and Releases
Semantic Versioning
# Chart.yaml
version: 1.2.3 # Chart version
appVersion: "2.0.0" # Application version
Packaging
helm package my-app
# Creates my-app-0.1.0.tgz
Hosting
# Create index
helm repo index . --url https://charts.example.com
# Or use Chart Museum
docker run -p 8080:8080 chartmuseum/chartmuseum
Best Practices
Use values.yaml Wisely
# Good: Sensible defaults
replicaCount: 1
# Good: Override for production
# values-production.yaml
replicaCount: 3
Resource Limits
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
Always set resource limits.
Labels
labels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
Standard labels for identification.
Notes.txt
# templates/NOTES.txt
Thank you for installing {{ .Chart.Name }}.
To get the application URL:
{{- if .Values.ingress.enabled }}
http://{{ .Values.ingress.hostname }}
{{- else }}
kubectl port-forward svc/{{ include "my-app.fullname" . }} 8080:80
{{- end }}
Helpful post-install instructions.
Helm vs Kustomize
| Aspect | Helm | Kustomize |
|---|---|---|
| Approach | Templating | Patching |
| Learning curve | Higher | Lower |
| Package management | Yes | No |
| Release management | Yes | No |
| Complexity | Higher | Lower |
Use Helm for complex apps. Kustomize for simple overlays.
Final Thoughts
Helm standardizes Kubernetes deployment. Instead of copying YAML files, you configure charts. Instead of manual rollbacks, you helm rollback.
Start by using existing charts. Graduate to writing your own when you understand the patterns.
Package once, deploy everywhere.