Kubernetes manifests can become hundreds of lines across dozens of files. Helm is the package manager that tames this complexity. Charts package your applications. Values customize deployments for different environments. Releases track deployments over time. In this lesson, you'll learn to create, package, and deploy Helm charts like a pro.
1. Learning Objectives
By the end of this lesson, you will be able to:
- Install and configure Helm
- Create Helm charts from scratch
- Use templates with Go templating
- Manage values for different environments
- Package and share charts
- Deploy and upgrade releases
- Use Helm repositories
2. Why This Matters
Real-world scenario: Your microservices app has 20 Kubernetes YAML files. Deploying to dev, staging, and production requires changing values in multiple files. Helm templatizes your manifests, lets you define values per environment, and deploys everything with one command.
3. Core Concepts
Helm Installation
# Install Helm (Linux)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# macOS
brew install helm
# Windows (choco)
choco install kubernetes-helm
# Verify installation
helm version
helm help
# Add repositories
helm repo add stable https://charts.helm.sh/stable
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Search for charts
helm search hub nginx
helm search repo nginx
# Install a chart
helm install my-nginx ingress-nginx/ingress-nginx
# List releases
helm list
helm list --all-namespaces
# Uninstall
helm uninstall my-nginx
Helm installation and basic commands
Chart Structure
# Create a new chart
helm create myapp
# Chart structure:
myapp/
├── Chart.yaml # Chart metadata
├── values.yaml # Default values
├── values-dev.yaml # Dev environment values (custom)
├── values-prod.yaml # Prod environment values (custom)
├── charts/ # Dependencies
├── templates/ # Kubernetes manifests
│ ├── NOTES.txt # Help text after install
│ ├── _helpers.tpl # Template helpers
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ └── tests/ # Test manifests
└── .helmignore # Files to ignore
# Examine chart contents
helm show chart myapp/
helm show values myapp/
Helm chart structure
Chart.yaml - Metadata
# Chart.yaml
apiVersion: v2
name: myapp
description: A Helm chart for my Kubernetes application
type: application
version: 0.1.0
appVersion: "1.16.0"
# Dependencies
dependencies:
- name: postgresql
version: 12.x.x
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: 17.x.x
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# Maintainers
maintainers:
- name: Your Name
email: your.email@example.com
# Keywords for search
keywords:
- myapp
- web
- backend
# Homepage
home: https://example.com
# Sources
sources:
- https://github.com/example/myapp
# Annotations (for Helm charts repository)
annotations:
artifacthub.io/license: MIT
artifacthub.io/operator: "false"
Chart.yaml configuration
Values.yaml - Default Configuration
# values.yaml
# Global settings
global:
environment: production
imagePullSecrets: []
# Replica count
replicaCount: 2
# Image configuration
image:
repository: myapp
pullPolicy: IfNotPresent
tag: latest
# Image pull secrets
imagePullSecrets: []
# Name override
nameOverride: ""
fullnameOverride: ""
# ServiceAccount
serviceAccount:
create: true
annotations: {}
name: ""
# Pod annotations
podAnnotations: {}
# Pod security context
podSecurityContext: {}
# fsGroup: 2000
# Container security context
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
# Service configuration
service:
type: ClusterIP
port: 80
targetPort: 8080
# Ingress configuration
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# Resource limits
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
# Autoscaling
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# Node selector
nodeSelector: {}
# Tolerations
tolerations: []
# Affinity
affinity: {}
# Environment variables (custom to your app)
env:
LOG_LEVEL: info
API_URL: https://api.example.com
# ConfigMap data
configMap:
enabled: true
data:
app.properties: |
app.name=myapp
app.version=1.0
# Secret data (base64 encoded)
secret:
enabled: true
data:
api-key: "" # Will be set via --set or external secrets
values.yaml - Default configuration
Templates with Go Templating
# templates/_helpers.tpl - Helper functions
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "myapp.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 "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
{{ include "myapp.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "myapp.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.targetPort }}
name: http
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
Helm templates with Go templating
Environment Values
# values-dev.yaml
replicaCount: 1
image:
tag: dev-latest
service:
type: ClusterIP
ingress:
enabled: false
env:
LOG_LEVEL: debug
API_URL: https://dev-api.example.com
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
autoscaling:
enabled: false
# values-staging.yaml
replicaCount: 2
image:
tag: staging-latest
service:
type: ClusterIP
ingress:
enabled: true
host: staging.example.com
env:
LOG_LEVEL: info
API_URL: https://staging-api.example.com
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
# values-prod.yaml
replicaCount: 5
image:
tag: latest
service:
type: LoadBalancer
ingress:
enabled: true
host: example.com
tls:
- hosts:
- example.com
secretName: tls-secret
env:
LOG_LEVEL: warn
API_URL: https://api.example.com
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
Environment-specific values files
4. Complete Project: Custom Helm Chart
# Create chart
helm create myapp-chart
cd myapp-chart
# Customize templates
# Edit templates/deployment.yaml, service.yaml, etc.
# Create environment values
cat > values-dev.yaml << 'EOF'
environment: dev
replicaCount: 1
ingress:
enabled: false
EOF
cat > values-staging.yaml << 'EOF'
environment: staging
replicaCount: 2
ingress:
enabled: true
host: staging.myapp.com
EOF
cat > values-prod.yaml << 'EOF'
environment: production
replicaCount: 5
ingress:
enabled: true
host: myapp.com
tls:
- secretName: myapp-tls
hosts:
- myapp.com
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
EOF
# Lint chart
helm lint .
# Template rendering (dry-run)
helm template myapp .
# Install with different environments
helm install myapp-dev . -f values-dev.yaml --namespace dev --create-namespace
helm install myapp-staging . -f values-staging.yaml --namespace staging --create-namespace
helm install myapp-prod . -f values-prod.yaml --namespace prod --create-namespace
# Override values on command line
helm install myapp . --set replicaCount=3 --set image.tag=v1.2.3
# Upgrade release
helm upgrade myapp-staging . -f values-staging.yaml --set image.tag=v1.2.4
# Rollback
helm rollback myapp-prod 2
# List releases
helm list -A
# Get release manifest
helm get manifest myapp-prod
# Get release values
helm get values myapp-prod
# Uninstall
helm uninstall myapp-dev -n dev
Complete Helm chart workflow
Helm Chart Dependencies
# Chart.yaml with dependencies
apiVersion: v2
name: myapp-with-deps
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "17.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
- name: prometheus
version: "23.x.x"
repository: "https://prometheus-community.github.io/helm-charts"
condition: monitoring.enabled
- name: grafana
version: "6.x.x"
repository: "https://grafana.github.io/helm-charts"
condition: monitoring.enabled
# Update dependencies
helm dependency update
# List dependencies
helm dependency list
# Build dependencies directory
helm dependency build
# values.yaml for dependencies
postgresql:
enabled: true
auth:
postgresPassword: "secret"
database: myapp
primary:
persistence:
size: 10Gi
redis:
enabled: true
architecture: standalone
auth:
password: "redis-secret"
monitoring:
enabled: false
prometheus:
server:
persistentVolume:
size: 50Gi
grafana:
adminPassword: admin
Managing chart dependencies
Packaging and Sharing
# Package chart
helm package myapp-chart
# Creates: myapp-chart-0.1.0.tgz
# Push to repository (OCI registry)
helm registry login registry.example.com
helm push myapp-chart-0.1.0.tgz oci://registry.example.com/helm
# Or use ChartMuseum (open-source Helm repo)
docker run -d -p 8080:8080 -e STORAGE=local -e STORAGE_LOCAL_ROOT=/charts chartmuseum/chartmuseum
# Push to ChartMuseum
curl --data-binary "@myapp-chart-0.1.0.tgz" http://localhost:8080/api/charts
# Add repository
helm repo add myrepo http://localhost:8080
helm repo update
# Install from repository
helm install myapp myrepo/myapp-chart
# Create GitHub Pages Helm repo
# 1. Create gh-pages branch
# 2. Place charts in repository
# 3. Generate index.yaml
helm repo index . --url https://username.github.io/repo
# Then add
helm repo add myrepo https://username.github.io/repo
Packaging and sharing Helm charts
5. Common Errors & Solutions
6. Summary Checklist
7. Next Steps
Next category: CI/CD - Advanced Pipelines (Lessons 7.2-7.5)
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment