[12/24] H is for Helm: Package Management for Kubernetes


This is Post #11 in the Kubernetes A-to-Z Series

Reading Order: Previous: Ingress | Next: Operators

Series Progress: 12/24 complete | Difficulty: Intermediate | Time: 20 min | Part 4/6: Advanced Concepts

Welcome to the eleventh post in our Kubernetes A-to-Z Series! Now that you understand Ingress, let’s explore Helm - the package manager for Kubernetes. Helm simplifies deploying and managing complex applications through reusable, versioned charts.

What is Helm?

Helm is often called “the package manager for Kubernetes.” It packages Kubernetes resources into charts - reusable, versioned bundles that can be easily shared and deployed.

Without Helm:
┌─────────────────────────────────────────────────┐
│  Manual Deployment                              │
│  kubectl apply -f deployment.yaml               │
│  kubectl apply -f service.yaml                  │
│  kubectl apply -f configmap.yaml                │
│  kubectl apply -f secret.yaml                   │
│  kubectl apply -f ingress.yaml                  │
│  ... (20+ more files)                           │
│  Manual version tracking                        │
│  Manual rollbacks                               │
└─────────────────────────────────────────────────┘

With Helm:
┌─────────────────────────────────────────────────┐
│  Helm Deployment                                │
│  helm install myapp ./mychart                   │
│  - Versioned releases                           │
│  - Easy rollbacks                               │
│  - Templated configurations                     │
│  - Dependency management                        │
└─────────────────────────────────────────────────┘

Helm Benefits

  • Simplified Deployments: Single command deploys entire applications
  • Version Control: Track releases and rollback easily
  • Templating: Parameterize manifests for different environments
  • Dependency Management: Bundle related charts together
  • Sharing: Public repositories for common applications

Helm Architecture

┌─────────────────────────────────────────────────┐
│  Helm CLI                                       │
│  ┌─────────────────────────────────────┐        │
│  │  helm install/upgrade/rollback      │        │
│  └──────────────┬──────────────────────┘        │
│                 │                               │
│                 ▼                               │
│  ┌─────────────────────────────────────┐        │
│  │  Chart + Values                     │        │
│  │  templates/ + values.yaml           │        │
│  └──────────────┬──────────────────────┘        │
│                 │                               │
│                 ▼                               │
│  ┌─────────────────────────────────────┐        │
│  │  Kubernetes API Server              │        │
│  │  (manifests applied)                │        │
│  └─────────────────────────────────────┘        │
└─────────────────────────────────────────────────┘

Installing Helm

# macOS
brew install helm

# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Windows (Chocolatey)
choco install kubernetes-helm

# Verify installation
helm version

Helm Charts

Chart Structure

mychart/
├── Chart.yaml          # Chart metadata
├── values.yaml         # Default configuration values
├── charts/             # Chart dependencies
├── templates/          # Kubernetes manifest templates
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── _helpers.tpl    # Template helpers
│   └── NOTES.txt       # Post-install notes
└── .helmignore         # Files to ignore

Chart.yaml

# Chart.yaml
apiVersion: v2
name: webapp
description: A Helm chart for deploying web applications
type: application
version: 1.0.0
appVersion: "2.0.0"
keywords:
  - webapp
  - nodejs
maintainers:
  - name: Platform Team
    email: [email protected]
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled

values.yaml

# values.yaml
replicaCount: 3

image:
  repository: myapp
  tag: "v1.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: myapp-tls
      hosts:
        - myapp.example.com

resources:
  requests:
    memory: "128Mi"
    cpu: "100m"
  limits:
    memory: "256Mi"
    cpu: "200m"

postgresql:
  enabled: true
  auth:
    database: webapp
    username: webapp

env:
  LOG_LEVEL: info
  CACHE_ENABLED: "true"

Template Examples

deployment.yaml:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "webapp.fullname" . }}
  labels:
    {{- include "webapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "webapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "webapp.selectorLabels" . | nindent 8 }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - containerPort: {{ .Values.service.targetPort }}
        env:
        {{- range $key, $value := .Values.env }}
        - name: {{ $key }}
          value: {{ $value | quote }}
        {{- end }}
        resources:
          {{- toYaml .Values.resources | nindent 12 }}

service.yaml:

# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "webapp.fullname" . }}
  labels:
    {{- include "webapp.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
  - port: {{ .Values.service.port }}
    targetPort: {{ .Values.service.targetPort }}
    protocol: TCP
  selector:
    {{- include "webapp.selectorLabels" . | nindent 4 }}

_helpers.tpl:

# templates/_helpers.tpl
{{- define "webapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "webapp.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 }}

{{- define "webapp.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ include "webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "webapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

Working with Charts

Installing Charts

# Install from local directory
helm install myapp ./mychart

# Install with custom values
helm install myapp ./mychart -f custom-values.yaml

# Install with inline values
helm install myapp ./mychart --set replicaCount=5 --set image.tag=v2.0.0

# Install in specific namespace
helm install myapp ./mychart -n production --create-namespace

# Install from repository
helm install nginx ingress-nginx/ingress-nginx

# Dry run (preview without installing)
helm install myapp ./mychart --dry-run --debug

Upgrading Releases

# Upgrade with new values
helm upgrade myapp ./mychart -f production-values.yaml

# Upgrade with inline values
helm upgrade myapp ./mychart --set image.tag=v2.0.0

# Install or upgrade (idempotent)
helm upgrade --install myapp ./mychart

# Upgrade with atomic rollback on failure
helm upgrade myapp ./mychart --atomic --timeout 5m

Rolling Back

# View release history
helm history myapp

# Rollback to previous version
helm rollback myapp

# Rollback to specific revision
helm rollback myapp 2

Managing Releases

# List releases
helm list
helm list -A  # All namespaces

# Get release status
helm status myapp

# Get release values
helm get values myapp
helm get values myapp --all

# Get rendered manifests
helm get manifest myapp

# Uninstall release
helm uninstall myapp
helm uninstall myapp --keep-history

Helm Repositories

# Add repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable https://charts.helm.sh/stable

# Update repositories
helm repo update

# Search repositories
helm search repo nginx
helm search repo bitnami/postgresql --versions

# Search Artifact Hub
helm search hub wordpress

Environment-Specific Values

# values-development.yaml
replicaCount: 1
image:
  tag: "latest"
resources:
  requests:
    memory: "64Mi"
    cpu: "50m"
ingress:
  hosts:
    - host: dev.myapp.example.com

# values-production.yaml
replicaCount: 5
image:
  tag: "v1.0.0"
resources:
  requests:
    memory: "256Mi"
    cpu: "200m"
  limits:
    memory: "512Mi"
    cpu: "500m"
ingress:
  hosts:
    - host: myapp.example.com
# Deploy to different environments
helm install myapp ./mychart -f values-development.yaml -n development
helm install myapp ./mychart -f values-production.yaml -n production

Chart Dependencies

# Chart.yaml
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
# Update dependencies
helm dependency update ./mychart

# Build dependencies
helm dependency build ./mychart

Creating Charts

# Create new chart
helm create mychart

# Package chart
helm package ./mychart

# Lint chart
helm lint ./mychart

# Template locally (debug)
helm template myapp ./mychart --debug

Helm Hooks

Execute actions at specific points in release lifecycle:

# templates/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "webapp.fullname" . }}-db-migrate
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        command: ["./migrate.sh"]
      restartPolicy: Never

Hook Types

HookDescription
pre-installBefore resources installed
post-installAfter resources installed
pre-upgradeBefore upgrade
post-upgradeAfter upgrade
pre-rollbackBefore rollback
post-rollbackAfter rollback
pre-deleteBefore deletion
post-deleteAfter deletion

Troubleshooting

# Debug installation
helm install myapp ./mychart --debug --dry-run

# Check release status
helm status myapp

# View release history
helm history myapp

# Get rendered templates
helm get manifest myapp

# Lint chart
helm lint ./mychart

# Template locally
helm template myapp ./mychart > rendered.yaml

Commands Reference

# Repository Management
helm repo add NAME URL
helm repo update
helm repo list
helm search repo KEYWORD

# Release Management
helm install RELEASE CHART
helm upgrade RELEASE CHART
helm rollback RELEASE [REVISION]
helm uninstall RELEASE
helm list
helm status RELEASE
helm history RELEASE

# Chart Development
helm create NAME
helm package CHART
helm lint CHART
helm template RELEASE CHART
helm dependency update CHART

# Values
helm get values RELEASE
helm install RELEASE CHART -f values.yaml
helm install RELEASE CHART --set key=value

Key Takeaways

  • Helm simplifies Kubernetes application deployment
  • Charts package related resources together
  • Values enable environment-specific configuration
  • Templates use Go templating for dynamic manifests
  • Releases track deployed chart versions
  • Hooks execute actions at lifecycle points
  • Dependencies manage chart relationships

Next Steps

Now that you understand Helm, you’re ready to explore Operators in the next post. We’ll learn how to extend Kubernetes functionality with custom controllers and resources.

Resources for Further Learning


Series Navigation:

Complete Series: Kubernetes A-to-Z Series Overview