Deploy Todo App with Helm

Package and Deploy Todo Application with Helm Charts

Learn templating, packaging, and environment-specific deployments with Helm.

What You’ll Learn

  • Helm charts - Package Kubernetes applications
  • Templating - DRY with Go templates and values
  • Values files - Environment-specific configurations
  • Chart structure - Best practices and organization
  • Release management - Install, upgrade, rollback

Prerequisites:

  • Completed Exercise 3 (Deploy with Kustomize)
  • Helm 3.x installed
  • Docker images available (from Exercise 2)
  • kubectl configured for Docker Desktop and/or EKS

Understanding Helm

What is Helm?

Helm is the package manager for Kubernetes - think of it as apt/yum/homebrew for K8s.

Key concepts:

  • Chart: Package of Kubernetes resources (like a .deb or .rpm)
  • Template: YAML with variables {{ .Values.something }}
  • Values: Configuration that varies per environment
  • Release: Deployed instance of a chart

Why Helm over Kustomize?

FeatureKustomizeHelm
TemplatingPatches/overlaysGo templates
ReusabilityGoodExcellent
ComplexitySimpleMore powerful
DependenciesManualBuilt-in
RollbackManualBuilt-in
PackagingNoYes
CommunityGrowingMassive

When to use Helm:

  • Need complex templating
  • Want to package and distribute charts
  • Need dependency management
  • Want built-in rollback
  • Deploying to many environments

When to use Kustomize:

  • Simpler deployments
  • Prefer pure YAML (no templating)
  • Already using kubectl
  • Want lighter tooling

Step 1: Install Helm

Verify Helm Installation

helm version

Expected output:

version.BuildInfo{Version:"v3.x.x", ...}

Install Helm (if needed)

macOS:

brew install helm

Linux:

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

Windows:

choco install kubernetes-helm

Step 2: Create Helm Chart Structure

2.1 Create Chart Directory

cd /path/to/implementation
mkdir -p "4. Deploy Todo App with Helm"
cd "4. Deploy Todo App with Helm"

# Create chart
helm create todo-app-chart

This creates the default structure. We’ll customize it.

2.2 Clean Up Default Files

cd todo-app-chart
rm -rf templates/*.yaml templates/tests

2.3 Final Chart Structure

todo-app-chart/
β”œβ”€β”€ Chart.yaml              # Chart metadata
β”œβ”€β”€ values.yaml             # Default values
β”œβ”€β”€ values-docker-desktop.yaml
β”œβ”€β”€ values-eks-dockerhub.yaml
β”œβ”€β”€ values-eks-ecr.yaml
└── templates/
    β”œβ”€β”€ _helpers.tpl        # Template helpers
    β”œβ”€β”€ namespace.yaml
    β”œβ”€β”€ storageclass.yaml
    β”œβ”€β”€ mongodb-statefulset.yaml
    β”œβ”€β”€ mongodb-service.yaml
    β”œβ”€β”€ mongodb-init-job.yaml
    β”œβ”€β”€ webapp-configmap.yaml
    β”œβ”€β”€ webapp-deployment.yaml
    β”œβ”€β”€ webapp-service.yaml
    β”œβ”€β”€ mongo-express-deployment.yaml
    └── mongo-express-service.yaml

Step 3: Create Chart Metadata

Chart.yaml

Create Chart.yaml:

apiVersion: v2
name: todo-app
description: A full-stack Todo application with MongoDB backend
type: application
version: 1.0.0
appVersion: "1.0.0"
keywords:
  - todo
  - mongodb
  - asp.net
maintainers:
  - name: Your Name
    email: your.email@example.com

Key fields:

  • apiVersion: v2 - Helm 3 chart
  • type: application - Deployable application (vs library)
  • version - Chart version (semver)
  • appVersion - Application version

Step 4: Create Template Helpers

templates/_helpers.tpl

Create templates/_helpers.tpl:

{{/*
Expand the name of the chart.
*/}}
{{- define "todo-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "todo-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 }}

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

{{/*
Selector labels
*/}}
{{- define "todo-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "todo-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

These helpers:

  • Generate consistent naming
  • Create standard labels
  • Enable chart reusability

Step 5: Create Default Values

values.yaml

Create values.yaml:

# Default values for todo-app chart
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

# Global settings
namespace: todo-app

# Container image configuration
image:
  repository: larsappel/todo-app
  tag: latest
  pullPolicy: Always

# MongoDB configuration
mongodb:
  enabled: true
  replicas: 1
  image: mongo:latest
  storage:
    enabled: true
    size: 1Gi
    # storageClass will be set per environment
    storageClass: ""
  resources:
    requests:
      memory: "256Mi"
      cpu: "250m"
    limits:
      memory: "512Mi"
      cpu: "500m"
  database: "ToDoAppDb"

# WebApp configuration
webapp:
  enabled: true
  replicas: 2
  port: 8080
  resources:
    requests:
      memory: "128Mi"
      cpu: "100m"
    limits:
      memory: "256Mi"
      cpu: "200m"
  service:
    type: LoadBalancer
    port: 80
    # AWS Load Balancer annotations (only for EKS)
    annotations: {}

# Mongo Express configuration
mongoExpress:
  enabled: true
  replicas: 1
  image: mongo-express:latest
  port: 8081
  username: admin
  password: pass
  resources:
    requests:
      memory: "128Mi"
      cpu: "100m"
    limits:
      memory: "256Mi"
      cpu: "200m"
  service:
    type: LoadBalancer
    port: 80
    annotations: {}

# Init job configuration
initJob:
  enabled: true
  backoffLimit: 4
  todos:
    - id: 1
      name: "Learn Kubernetes"
      isComplete: false
    - id: 2
      name: "Deploy MongoDB"
      isComplete: true

# Storage class (environment-specific)
storageClass:
  enabled: false
  name: ebs-sc
  provisioner: ebs.csi.eks.amazonaws.com
  volumeBindingMode: WaitForFirstConsumer
  parameters:
    type: gp3
    encrypted: "true"

Step 6: Create Templated Manifests

6.1 Namespace Template

Create templates/namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}

6.2 StorageClass Template

Create templates/storageclass.yaml:

{{- if .Values.storageClass.enabled }}
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: {{ .Values.storageClass.name }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
provisioner: {{ .Values.storageClass.provisioner }}
volumeBindingMode: {{ .Values.storageClass.volumeBindingMode }}
parameters:
  {{- toYaml .Values.storageClass.parameters | nindent 2 }}
{{- end }}

6.3 MongoDB StatefulSet Template

Create templates/mongodb-statefulset.yaml:

{{- if .Values.mongodb.enabled }}
---
apiVersion: v1
kind: Service
metadata:
  name: mongodb-service
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
    app: mongodb
spec:
  selector:
    app: mongodb
  ports:
  - port: 27017
    targetPort: 27017
  clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongodb
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
    app: mongodb
spec:
  serviceName: mongodb-service
  replicas: {{ .Values.mongodb.replicas }}
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
      - name: mongodb
        image: {{ .Values.mongodb.image }}
        ports:
        - containerPort: 27017
        volumeMounts:
        - name: mongodb-data
          mountPath: /data/db
        resources:
          {{- toYaml .Values.mongodb.resources | nindent 10 }}
  {{- if .Values.mongodb.storage.enabled }}
  volumeClaimTemplates:
  - metadata:
      name: mongodb-data
    spec:
      accessModes: ["ReadWriteOnce"]
      {{- if .Values.mongodb.storage.storageClass }}
      storageClassName: {{ .Values.mongodb.storage.storageClass }}
      {{- end }}
      resources:
        requests:
          storage: {{ .Values.mongodb.storage.size }}
  {{- end }}
{{- end }}

6.4 MongoDB Init Job Template

Create templates/mongodb-init-job.yaml:

{{- if .Values.initJob.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "todo-app.fullname" . }}-mongodb-init
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
    app: mongodb-init
spec:
  template:
    spec:
      containers:
      - name: mongo-init
        image: {{ .Values.mongodb.image }}
        command:
        - /bin/bash
        - -c
        - |
          sleep 10
          mongosh mongodb-service:27017/{{ .Values.mongodb.database }} --eval '
            db.TodoItems.insertMany([
              {{- range $index, $todo := .Values.initJob.todos }}
              {{- if $index }},{{ end }}
              {
                "Id": {{ $todo.id }},
                "Name": "{{ $todo.name }}",
                "IsComplete": {{ $todo.isComplete }}
              }
              {{- end }}
            ]);
            print("Database initialized successfully!");
          '          
      restartPolicy: OnFailure
  backoffLimit: {{ .Values.initJob.backoffLimit }}
{{- end }}

6.5 WebApp ConfigMap Template

Create templates/webapp-configmap.yaml:

{{- if .Values.webapp.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: webapp-config
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
data:
  MONGODB_HOST: "mongodb-service"
  MONGODB_PORT: "27017"
  MONGODB_DATABASE: {{ .Values.mongodb.database | quote }}
{{- end }}

6.6 WebApp Deployment Template

Create templates/webapp-deployment.yaml:

{{- if .Values.webapp.enabled }}
---
apiVersion: v1
kind: Service
metadata:
  name: todo-webapp-service
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
    app: todo-webapp
  {{- with .Values.webapp.service.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  type: {{ .Values.webapp.service.type }}
  selector:
    app: todo-webapp
  ports:
  - port: {{ .Values.webapp.service.port }}
    targetPort: {{ .Values.webapp.port }}
    {{- if and (eq .Values.webapp.service.type "NodePort") .Values.webapp.service.nodePort }}
    nodePort: {{ .Values.webapp.service.nodePort }}
    {{- end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo-webapp
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
    app: todo-webapp
spec:
  replicas: {{ .Values.webapp.replicas }}
  selector:
    matchLabels:
      app: todo-webapp
  template:
    metadata:
      labels:
        app: todo-webapp
    spec:
      containers:
      - name: webapp
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - containerPort: {{ .Values.webapp.port }}
        envFrom:
        - configMapRef:
            name: webapp-config
        resources:
          {{- toYaml .Values.webapp.resources | nindent 10 }}
{{- end }}

6.7 Mongo Express Deployment Template

Create templates/mongo-express-deployment.yaml:

{{- if .Values.mongoExpress.enabled }}
---
apiVersion: v1
kind: Service
metadata:
  name: mongo-express-service
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
    app: mongo-express
  {{- with .Values.mongoExpress.service.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  type: {{ .Values.mongoExpress.service.type }}
  selector:
    app: mongo-express
  ports:
  - port: {{ .Values.mongoExpress.service.port }}
    targetPort: {{ .Values.mongoExpress.port }}
    {{- if and (eq .Values.mongoExpress.service.type "NodePort") .Values.mongoExpress.service.nodePort }}
    nodePort: {{ .Values.mongoExpress.service.nodePort }}
    {{- end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongo-express
  namespace: {{ .Values.namespace }}
  labels:
    {{- include "todo-app.labels" . | nindent 4 }}
    app: mongo-express
spec:
  replicas: {{ .Values.mongoExpress.replicas }}
  selector:
    matchLabels:
      app: mongo-express
  template:
    metadata:
      labels:
        app: mongo-express
    spec:
      containers:
      - name: mongo-express
        image: {{ .Values.mongoExpress.image }}
        ports:
        - containerPort: {{ .Values.mongoExpress.port }}
        env:
        - name: ME_CONFIG_MONGODB_URL
          value: "mongodb://mongodb-service:27017"
        - name: ME_CONFIG_BASICAUTH_USERNAME
          value: {{ .Values.mongoExpress.username | quote }}
        - name: ME_CONFIG_BASICAUTH_PASSWORD
          value: {{ .Values.mongoExpress.password | quote }}
        resources:
          {{- toYaml .Values.mongoExpress.resources | nindent 10 }}
{{- end }}

Step 7: Create Environment-Specific Values Files

7.1 Docker Desktop Values

Create values-docker-desktop.yaml:

# Docker Desktop environment configuration

# Use Docker Hub image
image:
  repository: larsappel/todo-app
  tag: latest

# MongoDB storage - use hostpath
mongodb:
  storage:
    storageClass: hostpath

# WebApp service - NodePort for local access
webapp:
  service:
    type: NodePort
    nodePort: 30080

# Mongo Express service - NodePort for local access
mongoExpress:
  service:
    type: NodePort
    nodePort: 30081

# Don't create StorageClass (use built-in hostpath)
storageClass:
  enabled: false

7.2 EKS with Docker Hub Values

Create values-eks-dockerhub.yaml:

# EKS environment with Docker Hub image

# Use Docker Hub image
image:
  repository: larsappel/todo-app
  tag: latest

# MongoDB storage - use EBS
mongodb:
  storage:
    storageClass: ebs-sc

# WebApp service - LoadBalancer with AWS annotations
webapp:
  service:
    type: LoadBalancer
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing

# Mongo Express service - LoadBalancer with AWS annotations
mongoExpress:
  service:
    type: LoadBalancer
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing

# Create EBS StorageClass
storageClass:
  enabled: true
  name: ebs-sc
  provisioner: ebs.csi.eks.amazonaws.com
  volumeBindingMode: WaitForFirstConsumer
  parameters:
    type: gp3
    encrypted: "true"

7.3 EKS with ECR Values

Create values-eks-ecr.yaml:

# EKS environment with ECR private registry

# Use ECR image
image:
  repository: 880731366811.dkr.ecr.eu-west-1.amazonaws.com/todo-app
  tag: latest

# MongoDB storage - use EBS
mongodb:
  storage:
    storageClass: ebs-sc

# WebApp service - LoadBalancer with AWS annotations
webapp:
  service:
    type: LoadBalancer
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing

# Mongo Express service - LoadBalancer with AWS annotations
mongoExpress:
  service:
    type: LoadBalancer
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing

# Create EBS StorageClass
storageClass:
  enabled: true
  name: ebs-sc
  provisioner: ebs.csi.eks.amazonaws.com
  volumeBindingMode: WaitForFirstConsumer
  parameters:
    type: gp3
    encrypted: "true"

Step 8: Validate and Preview Helm Chart

8.1 Lint the Chart

cd /path/to/todo-app-chart
helm lint .

Expected output:

==> Linting .
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed

8.2 Preview Rendered Templates

For Docker Desktop:

helm template my-todo-app . -f values-docker-desktop.yaml

For EKS with Docker Hub:

helm template my-todo-app . -f values-eks-dockerhub.yaml

For EKS with ECR:

helm template my-todo-app . -f values-eks-ecr.yaml

This shows exactly what YAML will be deployed.

8.3 Dry Run

# Dry run for Docker Desktop
helm install my-todo-app . -f values-docker-desktop.yaml --dry-run --debug

Step 9: Deploy to Docker Desktop

9.1 Switch Context

kubectl config use-context docker-desktop

9.2 Install Chart

cd /path/to/todo-app-chart

helm install todo-app-local . -f values-docker-desktop.yaml

Expected output:

NAME: todo-app-local
LAST DEPLOYED: ...
NAMESPACE: default
STATUS: deployed
REVISION: 1

9.3 Verify Deployment

# List Helm releases
helm list

# Check resources
kubectl get all -n todo-app

# Check PVC
kubectl get pvc -n todo-app

9.4 Wait for Pods

kubectl wait --for=condition=ready pod -l app=mongodb -n todo-app --timeout=180s
kubectl wait --for=condition=ready pod -l app=todo-webapp -n todo-app --timeout=60s

9.5 Test Application

Todo App:

curl http://localhost:30080/api/todos

Mongo Express:

http://localhost:30081

Login: admin / pass


Step 10: Deploy to EKS with Docker Hub

10.1 Switch to EKS Context

kubectl config use-context <your-eks-context>

10.2 Install Chart

cd /path/to/todo-app-chart

helm install todo-app-eks . -f values-eks-dockerhub.yaml

10.3 Monitor Deployment

# Watch pods
kubectl get pods -n todo-app -w

# Check services
kubectl get svc -n todo-app

10.4 Get LoadBalancer URLs

# WebApp URL
kubectl get svc todo-webapp-service -n todo-app -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'

# Mongo Express URL
kubectl get svc mongo-express-service -n todo-app -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'

10.5 Test from Internet

WEBAPP_URL=$(kubectl get svc todo-webapp-service -n todo-app -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

curl http://${WEBAPP_URL}/api/todos

Step 11: Upgrade to ECR Image

11.1 Upgrade Release

helm upgrade todo-app-eks . -f values-eks-ecr.yaml

What happens:

  • Helm calculates diff between current and desired state
  • Updates only changed resources
  • Keeps revision history for rollback

11.2 Check Revision History

helm history todo-app-eks

Output:

REVISION  UPDATED                   STATUS      CHART           DESCRIPTION
1         ...                       superseded  todo-app-1.0.0  Install complete
2         ...                       deployed    todo-app-1.0.0  Upgrade complete

11.3 Verify Image Changed

kubectl describe deployment todo-webapp -n todo-app | grep Image:

Should show ECR image URL.


Step 12: Helm Release Management

12.1 View Release Status

helm status todo-app-eks

Shows current state, resources, and notes.

12.2 Rollback to Previous Version

# Rollback to revision 1 (Docker Hub image)
helm rollback todo-app-eks 1

12.3 Get Values from Release

# Show all values used in current release
helm get values todo-app-eks

# Show all values including defaults
helm get values todo-app-eks --all

12.4 Uninstall Release

# Delete release (keeps history)
helm uninstall todo-app-eks

# Delete release and purge history
helm uninstall todo-app-eks --purge

Step 13: Advanced Helm Features

13.1 Override Values via CLI

# Change replica count on the fly
helm install todo-app . \
  -f values-docker-desktop.yaml \
  --set webapp.replicas=3 \
  --set mongoExpress.enabled=false

13.2 Use Multiple Values Files

# Combine base + environment + custom overrides
helm install todo-app . \
  -f values.yaml \
  -f values-eks-dockerhub.yaml \
  -f values-custom.yaml

Later files override earlier ones.

13.3 Template Functions

Check if a value is set:

{{- if .Values.webapp.ingress.enabled }}
# Include ingress resource
{{- end }}

Default values:

replicas: {{ .Values.webapp.replicas | default 2 }}

Loops:

{{- range .Values.initJob.todos }}
- id: {{ .id }}
  name: {{ .name }}
{{- end }}

13.4 Chart Dependencies

Add to Chart.yaml:

dependencies:
  - name: mongodb
    version: "13.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: mongodb.enabled

Then:

helm dependency update
helm install todo-app .

Step 14: Compare Helm vs Kustomize

Side-by-Side Comparison

Same task - Change image tag:

Kustomize:

# In kustomization.yaml
images:
  - name: todo-app
    newName: larsappel/todo-app
    newTag: v2.0.0  # Manual edit needed

Helm:

# Command line - no file edit
helm upgrade todo-app . --set image.tag=v2.0.0

Same task - Enable/disable Mongo Express:

Kustomize:

# Need to manually edit kustomization.yaml
# Comment out mongo-express resources

Helm:

# Single flag
helm upgrade todo-app . --set mongoExpress.enabled=false

Same task - Different environments:

Kustomize:

kubectl apply -k overlays/docker-desktop
kubectl apply -k overlays/eks-dockerhub
kubectl apply -k overlays/eks-ecr

Helm:

helm install todo-app . -f values-docker-desktop.yaml
helm install todo-app . -f values-eks-dockerhub.yaml
helm install todo-app . -f values-eks-ecr.yaml

Step 15: Troubleshooting

Chart Won’t Install

Symptom: Error: YAML parse error

Check:

# Validate YAML syntax
helm lint .

# Use --debug to see rendered templates
helm install todo-app . --debug --dry-run

Template Rendering Issues

Symptom: Variables not replacing

Check:

# Preview exact output
helm template todo-app . -f values-docker-desktop.yaml > output.yaml
cat output.yaml

Common issues:

  • Wrong indentation in templates
  • Missing values in values file
  • Incorrect template syntax {{ }} vs {{- }}

Upgrade Fails

Symptom: Error: UPGRADE FAILED

Check:

# See what changed
helm diff upgrade todo-app . -f values-eks-ecr.yaml

# View current values
helm get values todo-app

# Rollback if needed
helm rollback todo-app

Release Stuck

Symptom: Helm install hangs

Check:

# See Kubernetes events
kubectl get events -n todo-app --sort-by='.lastTimestamp'

# Check pod status
kubectl get pods -n todo-app
kubectl describe pod <pod-name> -n todo-app

Step 16: Cleanup

Delete Helm Releases

Docker Desktop:

helm uninstall todo-app-local
kubectl delete namespace todo-app

EKS:

helm uninstall todo-app-eks
kubectl delete namespace todo-app
kubectl delete storageclass ebs-sc

Key Takeaways

Helm Benefits

βœ… Templating Power: Go templates with functions, loops, conditionals βœ… Reusability: Package once, deploy anywhere βœ… Release Management: Built-in versioning, rollback, history βœ… Dependency Management: Declare chart dependencies βœ… Community: Massive library of public charts βœ… Packaging: Distribute charts as .tgz archives

When to Use Helm

βœ… Good for:

  • Complex applications with many environments
  • Need to distribute/share applications
  • Want built-in rollback capability
  • Need dependency management
  • Deploying from CI/CD pipelines
  • Using community charts

❌ Overkill for:

  • Very simple deployments
  • Only deploying to one environment
  • Team prefers plain YAML
  • Don’t need templating

Helm vs Kustomize Summary

AspectKustomizeHelm
Learning curveLowerHigher
FlexibilityGoodExcellent
ToolingBuilt into kubectlSeparate tool
CommunityGrowingMassive
Best forSimple to mediumMedium to complex

You can use both! Many teams use Kustomize for simple apps and Helm for complex ones.


Next Steps

Now that you’ve mastered Helm, you can:

  1. Publish charts - Create a Helm repository (GitHub Pages, ChartMuseum)
  2. Use public charts - Leverage community charts (Bitnami, etc.)
  3. Chart hooks - Pre/post install/upgrade actions
  4. GitOps with ArgoCD - Automate Helm deployments from Git
  5. Helmfile - Manage multiple Helm releases declaratively

Congratulations! πŸŽ‰

You’ve successfully:

  • βœ… Created a production-ready Helm chart
  • βœ… Templated Kubernetes manifests with Go templates
  • βœ… Deployed to 3 different environments with values files
  • βœ… Managed releases with install, upgrade, rollback
  • βœ… Understood when to use Helm vs Kustomize

You’re now ready for production Helm deployments and GitOps!


Reference Commands

Chart Development

helm create <chart-name>          # Create new chart
helm lint .                        # Validate chart
helm template <name> .             # Preview rendered templates
helm install <name> . --dry-run    # Test without installing

Release Management

helm install <name> . -f values.yaml     # Install release
helm upgrade <name> . -f values.yaml     # Upgrade release
helm rollback <name> <revision>          # Rollback to revision
helm uninstall <name>                    # Delete release

Release Information

helm list                          # List all releases
helm status <name>                 # Show release status
helm history <name>                # Show revision history
helm get values <name>             # Get current values
helm get manifest <name>           # Get deployed manifest

Chart Repository

helm repo add <name> <url>         # Add chart repository
helm repo update                   # Update repositories
helm search repo <chart>           # Search for charts
helm pull <chart>                  # Download chart