Kubernetes Helm Chart Tasks¶
- Hands-on Kubernetes exercises covering Helm chart creation, packaging, deployment, and best practices.
- Each task includes a description, scenario, and a detailed solution with step-by-step instructions.
- Practice these tasks to master Helm from basic chart scaffolding to advanced templating and chart repositories.
Table of Contents¶
- 01. Scaffold a Helm Chart
- 02. Explore the Chart Structure
- 03. Deploy an Nginx-Based Chart
- 04. Customize the Welcome Page with Current Date & Time
- 05. Add a Service
- 06. Add Two Ingress Resources with Different Paths
- 07. Add an ExternalName Service
- 08. Values Overrides and Environments
- 09. Template Helpers and Named Templates
- 10. Template Control Flow (if / range / with)
- 11. Chart Dependencies (Subcharts)
- 12. Linting, Dry-Run, and Debugging
- 13. Package and Host a Chart Repository
- 14. Upgrade, Rollback, and Release History
- 15. Hooks (Pre-install / Post-install)
- 16. Use
helm statusto Inspect a Release - 17. Extract Values with
helm get values - 18. Show Chart Values with
helm show values - 19. Search Charts with
helm search repo - 20. Update Repositories with
helm repo update - 21. Run Chart Tests with
helm test - 22. Use
helm get allto Retrieve Complete Release Info - 23. Use
helm listwith Filters and Formatting - 24. Chain Multiple Commands for Release Management
01. Scaffold a Helm Chart¶
Create a new Helm chart from scratch using helm create and explore the generated files.
Scenario:¶
◦ You need to package an application for Kubernetes and want a standardized project structure. ◦ helm create generates a best-practice skeleton you can customize.
Hint: helm create, tree
Solution
# 1. Create a new chart named "my-nginx-app"
helm create my-nginx-app
# 2. Explore the generated structure
tree my-nginx-app/
# Output:
# my-nginx-app/
# ├── Chart.yaml # Chart metadata (name, version, description)
# ├── values.yaml # Default configuration values
# ├── charts/ # Dependency charts (subcharts)
# ├── templates/ # Kubernetes manifest templates
# │ ├── NOTES.txt # Post-install usage notes
# │ ├── _helpers.tpl # Named template definitions
# │ ├── deployment.yaml
# │ ├── hpa.yaml
# │ ├── ingress.yaml
# │ ├── service.yaml
# │ ├── serviceaccount.yaml
# │ └── tests/
# │ └── test-connection.yaml
# └── .helmignore # Files to exclude when packaging
02. Explore the Chart Structure¶
Inspect Chart.yaml and values.yaml to understand how Helm charts are configured.
Scenario:¶
◦ Before modifying a chart, you need to understand what each file does. ◦ Chart.yaml defines the chart identity; values.yaml drives all the template rendering.
Hint: cat Chart.yaml, cat values.yaml
Solution
# 1. Inspect Chart.yaml
cat my-nginx-app/Chart.yaml
# Key fields:
# - apiVersion: v2 (Helm 3 chart)
# - name: my-nginx-app
# - version: 0.1.0 (chart version - bump this on changes)
# - appVersion: "1.16.0" (the app version being deployed)
# 2. Inspect values.yaml
cat my-nginx-app/values.yaml
# Key fields:
# - replicaCount: 1
# - image.repository: nginx
# - image.tag: "" (defaults to appVersion from Chart.yaml)
# - service.type: ClusterIP
# - service.port: 80
# - ingress.enabled: false
# 3. See how values are consumed in templates
grep -n '{{ .Values' my-nginx-app/templates/deployment.yaml
# 4. Render the templates without deploying (dry-run)
helm template my-release my-nginx-app/
03. Deploy an Nginx-Based Chart¶
Install the chart to your cluster using the default nginx image, verify the deployment, and access nginx.
Scenario:¶
◦ You want to deploy a basic nginx web server using Helm to validate the chart works before customizing it.
Hint: helm install, kubectl get all, kubectl port-forward
Solution
# 1. Install the chart
helm install my-nginx my-nginx-app/
# 2. Verify all resources were created
kubectl get all -l app.kubernetes.io/instance=my-nginx
# 3. Check the release
helm list
# 4. Access the application via port-forward
kubectl port-forward svc/my-nginx-my-nginx-app 8080:80
# 5. In another terminal or browser
curl http://localhost:8080
# Should show the default nginx welcome page
# 6. Uninstall when done
helm uninstall my-nginx
04. Customize the Welcome Page with Current Date & Time¶
Create a ConfigMap that generates a custom HTML welcome page showing the current date and time, and mount it into the nginx container.
Scenario:¶
◦ You want to display dynamic content (deployment timestamp) on the nginx welcome page. ◦ This demonstrates how Helm templates can inject build-time values into application configuration.
Hint: now, date, ConfigMap volume mount, {{ .Release }}
Solution
Step 1: Create the ConfigMap template [templates/configmap-html.yaml]¶
cat > my-nginx-app/templates/configmap-html.yaml << 'TEMPLATE'
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "my-nginx-app.fullname" . }}-html
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>{{ .Values.welcomePage.title | default "Welcome" }}</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container { text-align: center; }
h1 { font-size: 2.5em; }
.info { font-size: 1.2em; margin: 10px 0; }
.time { font-size: 3em; font-weight: bold; margin: 20px 0; }
</style>
</head>
<body>
<div class="container">
<h1>{{ .Values.welcomePage.title | default "Welcome to My Nginx App" }}</h1>
<p class="info">Release: <strong>{{ .Release.Name }}</strong></p>
<p class="info">Namespace: <strong>{{ .Release.Namespace }}</strong></p>
<p class="info">Chart Version: <strong>{{ .Chart.Version }}</strong></p>
<p class="info">App Version: <strong>{{ .Chart.AppVersion }}</strong></p>
<p class="time">Deployed at: {{ now | date "2006-01-02 15:04:05 MST" }}</p>
{{- if .Values.welcomePage.message }}
<p class="info">{{ .Values.welcomePage.message }}</p>
{{- end }}
</div>
</body>
</html>
TEMPLATE
Step 2: Add welcome page values to values.yaml¶
cat >> my-nginx-app/values.yaml << 'EOF'
# Custom welcome page configuration
welcomePage:
title: "Welcome to My Nginx App"
message: "Deployed with Helm!"
EOF
Step 3: Update the deployment template to mount the ConfigMap¶
Edit my-nginx-app/templates/deployment.yaml - add the volumeMounts and volumes:
# Add volumeMounts under the container spec and volumes under the pod spec.
# The easiest approach: replace the deployment template entirely.
# Here we patch the key sections:
# In the container spec, add:
# volumeMounts:
# - name: html-volume
# mountPath: /usr/share/nginx/html
# readOnly: true
# In the pod spec (same level as containers), add:
# volumes:
# - name: html-volume
# configMap:
# name: {{ include "my-nginx-app.fullname" . }}-html
For a quick approach, replace the entire deployment template:
cat > my-nginx-app/templates/deployment.yaml << 'TEMPLATE'
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-nginx-app.fullname" . }}
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-nginx-app.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
# Force pod restart on ConfigMap changes
checksum/html: {{ include (print $.Template.BasePath "/configmap-html.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "my-nginx-app.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "my-nginx-app.serviceAccountName" . }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: html-volume
mountPath: /usr/share/nginx/html
readOnly: true
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: html-volume
configMap:
name: {{ include "my-nginx-app.fullname" . }}-html
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
TEMPLATE
Step 4: Install and verify¶
# Install (or upgrade if already installed)
helm upgrade --install my-nginx my-nginx-app/
# Port-forward and check the custom page
kubectl port-forward svc/my-nginx-my-nginx-app 8080:80
# In another terminal
curl http://localhost:8080
# Should show the custom HTML page with current date/time and release info
05. Add a Service¶
Verify the Service template supports ClusterIP, NodePort, and LoadBalancer types via values.yaml.
Scenario:¶
◦ You want to expose your application with different service types depending on the environment (e.g., ClusterIP for dev, NodePort for minikube, LoadBalancer for cloud). ◦ You need your chart to be flexible enough to deploy with different service types depending on the environment. ◦ The default helm create already includes a service template - you need to understand and test it.
Hint: --set service.type=NodePort, helm upgrade
Solution
# 1. Check the current service template
cat my-nginx-app/templates/service.yaml
# 2. The default template already supports configurable type:
# type: {{ .Values.service.type }}
# port: {{ .Values.service.port }}
# 3. Install with ClusterIP (default)
helm upgrade --install my-nginx my-nginx-app/
# 4. Verify
kubectl get svc -l app.kubernetes.io/instance=my-nginx
# TYPE should be ClusterIP
# 5. Upgrade to NodePort
helm upgrade my-nginx my-nginx-app/ --set service.type=NodePort
# 6. Verify
kubectl get svc -l app.kubernetes.io/instance=my-nginx
# TYPE should now be NodePort
# 7. Upgrade to LoadBalancer
helm upgrade my-nginx my-nginx-app/ --set service.type=LoadBalancer
# 8. Verify
kubectl get svc -l app.kubernetes.io/instance=my-nginx
# TYPE should now be LoadBalancer (EXTERNAL-IP may stay <pending> on local clusters)
06. Add Two Ingress Resources with Different Paths¶
Create two Ingress resources: one serving the main app at / and another serving a health/status endpoint at /status.
Scenario:¶
◦ Your application has a main frontend and a separate status/health page. ◦ You want to route traffic using different URL paths to the same backend, each with its own Ingress resource. ◦ This is useful when different Ingress resources need different annotations (rate limiting, auth, etc.).
Prerequisites: An Ingress controller must be installed (e.g., nginx-ingress).
Hint: Two separate Ingress templates, pathType: Prefix
Solution
Step 1: Create the main Ingress template [templates/ingress-main.yaml]
cat > my-nginx-app/templates/ingress-main.yaml << 'TEMPLATE'
{{- if .Values.ingress.main.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-nginx-app.fullname" . }}-main
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
{{- with .Values.ingress.main.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.main.className }}
ingressClassName: {{ .Values.ingress.main.className }}
{{- end }}
rules:
- host: {{ .Values.ingress.main.host | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "my-nginx-app.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- if .Values.ingress.main.tls }}
tls:
{{- toYaml .Values.ingress.main.tls | nindent 4 }}
{{- end }}
{{- end }}
TEMPLATE
Step 2: Create the status Ingress template [templates/ingress-status.yaml]
cat > my-nginx-app/templates/ingress-status.yaml << 'TEMPLATE'
{{- if .Values.ingress.status.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-nginx-app.fullname" . }}-status
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
{{- with .Values.ingress.status.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.status.className }}
ingressClassName: {{ .Values.ingress.status.className }}
{{- end }}
rules:
- host: {{ .Values.ingress.status.host | quote }}
http:
paths:
- path: /status
pathType: Prefix
backend:
service:
name: {{ include "my-nginx-app.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- if .Values.ingress.status.tls }}
tls:
{{- toYaml .Values.ingress.status.tls | nindent 4 }}
{{- end }}
{{- end }}
TEMPLATE
Step 3: Remove the default ingress template (optional) and update values.yaml
# Remove the default generated ingress template to avoid confusion
rm my-nginx-app/templates/ingress.yaml
# Add ingress values to values.yaml
cat >> my-nginx-app/values.yaml << 'EOF'
# Ingress configuration - two separate Ingress resources
ingress:
main:
enabled: true
className: nginx
host: my-nginx.local
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
tls: []
status:
enabled: true
className: nginx
host: my-nginx.local
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
# Example: different rate limit for status endpoint
nginx.ingress.kubernetes.io/limit-rps: "10"
tls: []
EOF
Step 4: Deploy and verify
# Upgrade the release
helm upgrade --install my-nginx my-nginx-app/
# Verify both Ingress resources were created
kubectl get ingress -l app.kubernetes.io/instance=my-nginx
# Expected output:
# NAME CLASS HOSTS ADDRESS PORTS AGE
# my-nginx-my-nginx-app-main nginx my-nginx.local 80 5s
# my-nginx-my-nginx-app-status nginx my-nginx.local 80 5s
# Describe each to see the path rules
kubectl describe ingress my-nginx-my-nginx-app-main
kubectl describe ingress my-nginx-my-nginx-app-status
# Test (add host entry or use curl with Host header)
# curl -H "Host: my-nginx.local" http://<INGRESS_IP>/
# curl -H "Host: my-nginx.local" http://<INGRESS_IP>/status
07. Add an ExternalName Service¶
Add a second Service of type ExternalName that maps a Kubernetes service name to an external DNS name.
Scenario:¶
◦ Your application needs to connect to an external API or database (e.g., an RDS instance, a SaaS endpoint). ◦ By using an ExternalName service, you can refer to it by a local name inside the cluster and change the target later without modifying application code.
Hint: type: ExternalName, externalName
Solution
Step 1: Create the ExternalName Service template [templates/service-external.yaml]
cat > my-nginx-app/templates/service-external.yaml << 'TEMPLATE'
{{- if .Values.externalService.enabled -}}
apiVersion: v1
kind: Service
metadata:
name: {{ include "my-nginx-app.fullname" . }}-external
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
spec:
type: ExternalName
externalName: {{ .Values.externalService.host | quote }}
{{- if .Values.externalService.ports }}
ports:
{{- toYaml .Values.externalService.ports | nindent 4 }}
{{- end }}
{{- end }}
TEMPLATE
Step 2: Add values to values.yaml
cat >> my-nginx-app/values.yaml << 'EOF'
# ExternalName service - maps a local name to an external DNS
externalService:
enabled: true
host: api.example.com
ports:
- port: 443
protocol: TCP
EOF
Step 3: Deploy and verify
# Upgrade the release
helm upgrade --install my-nginx my-nginx-app/
# Verify the ExternalName service
kubectl get svc -l app.kubernetes.io/instance=my-nginx
# Should show something like:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# my-nginx-my-nginx-app ClusterIP 10.x.x.x <none> 80/TCP
# my-nginx-my-nginx-app-external ExternalName <none> api.example.com 443/TCP
# Test DNS resolution from inside the cluster
kubectl run dns-check --image=busybox --restart=Never \
-- nslookup my-nginx-my-nginx-app-external
kubectl logs dns-check
# Should resolve to api.example.com
# Cleanup test pod
kubectl delete pod dns-check
08. Values Overrides and Environments¶
Use multiple values files to manage different environments (dev, staging, production).
Scenario:¶
◦ You have one chart but need different configurations per environment (replica count, image tag, resource limits). ◦ Helm supports layering multiple -f values files and --set overrides.
Hint: helm install -f, --set, multiple values files
Solution
# 1. Create a dev values file
cat > values-dev.yaml << 'EOF'
replicaCount: 1
image:
tag: "alpine"
resources:
limits:
cpu: 100m
memory: 128Mi
welcomePage:
title: "DEV Environment"
message: "This is the development instance"
EOF
# 2. Create a production values file
cat > values-prod.yaml << 'EOF'
replicaCount: 3
image:
tag: "stable"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
welcomePage:
title: "PRODUCTION"
message: "Production instance - handle with care"
service:
type: LoadBalancer
EOF
# 3. Install with dev values
helm upgrade --install my-nginx-dev my-nginx-app/ \
-f values-dev.yaml \
--namespace dev --create-namespace
# 4. Install with prod values
helm upgrade --install my-nginx-prod my-nginx-app/ \
-f values-prod.yaml \
--namespace prod --create-namespace
# 5. Verify different configurations
kubectl get deployment -n dev -o wide
kubectl get deployment -n prod -o wide
# 6. Override a single value on top of a values file
helm upgrade my-nginx-dev my-nginx-app/ \
-f values-dev.yaml \
--set replicaCount=2 \
--namespace dev
# Cleanup
helm uninstall my-nginx-dev -n dev
helm uninstall my-nginx-prod -n prod
kubectl delete ns dev prod
09. Template Helpers and Named Templates¶
Create a custom named template in _helpers.tpl and use it across multiple templates.
Scenario:¶
◦ You have repeated logic (e.g., generating labels, resource names) across templates. ◦ Named templates (partials) in _helpers.tpl let you define reusable snippets.
Hint: define, include, _helpers.tpl
Solution
# 1. Inspect existing helpers
cat my-nginx-app/templates/_helpers.tpl
# You'll see templates like:
# {{- define "my-nginx-app.name" -}} → Chart name
# {{- define "my-nginx-app.fullname" -}} → Release-qualified name
# {{- define "my-nginx-app.labels" -}} → Standard labels
# {{- define "my-nginx-app.selectorLabels" -}} → Selector labels
# 2. Add a custom helper - e.g., environment label
cat >> my-nginx-app/templates/_helpers.tpl << 'EOF'
{{/*
Custom: Generate environment-specific annotations
*/}}
{{- define "my-nginx-app.envAnnotations" -}}
app.kubernetes.io/environment: {{ .Values.environment | default "dev" }}
app.kubernetes.io/team: {{ .Values.team | default "platform" }}
{{- end }}
EOF
# 3. Use it in a template (e.g., deployment.yaml metadata.annotations):
# annotations:
# {{- include "my-nginx-app.envAnnotations" . | nindent 4 }}
# 4. Add default values
cat >> my-nginx-app/values.yaml << 'EOF'
# Environment metadata
environment: dev
team: platform
EOF
# 5. Test rendering
helm template my-nginx my-nginx-app/ | grep -A2 "environment"
10. Template Control Flow (if / range / with)¶
Practice Helm template control structures: conditionals, loops, and scoping.
Scenario:¶
◦ You need to conditionally render resources, iterate over lists, or scope into nested values. ◦ Go template control flow is essential for writing flexible Helm charts.
Hint: {{- if }}, {{- range }}, {{- with }}
Solution
# 1. Conditional: only create a resource if enabled
# Already used in ingress templates:
# {{- if .Values.ingress.main.enabled -}}
# ...
# {{- end }}
# 2. Range: iterate over a list
# Example: Add multiple environment variables from a values list
# In values.yaml:
cat >> my-nginx-app/values.yaml << 'EOF'
# Extra environment variables
extraEnv:
- name: LOG_LEVEL
value: "info"
- name: APP_MODE
value: "production"
EOF
# In deployment.yaml, under containers[].env:
# {{- range .Values.extraEnv }}
# - name: {{ .name }}
# value: {{ .value | quote }}
# {{- end }}
# 3. With: scope into a map
# {{- with .Values.nodeSelector }}
# nodeSelector:
# {{- toYaml . | nindent 8 }}
# {{- end }}
# 4. Test the rendering
helm template my-nginx my-nginx-app/ --set extraEnv[0].name=DEBUG,extraEnv[0].value=true
11. Chart Dependencies (Subcharts)¶
Add a dependency (e.g., Redis) as a subchart and configure it through the parent values.yaml.
Scenario:¶
◦ Your application needs a Redis cache alongside nginx. ◦ Instead of writing Redis manifests from scratch, you depend on an existing chart from a repository.
Hint: Chart.yaml dependencies, helm dependency update
Solution
# 1. Add dependency to Chart.yaml
cat >> my-nginx-app/Chart.yaml << 'EOF'
dependencies:
- name: redis
version: "~18.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
EOF
# 2. Add redis configuration in values.yaml
cat >> my-nginx-app/values.yaml << 'EOF'
# Redis subchart configuration
redis:
enabled: false # Set to true to deploy Redis alongside nginx
architecture: standalone
auth:
enabled: false
EOF
# 3. Build dependencies (downloads the redis chart into charts/)
helm dependency update my-nginx-app/
# 4. Verify
ls my-nginx-app/charts/
# Should show: redis-18.x.x.tgz
# 5. Install with Redis enabled
helm upgrade --install my-nginx my-nginx-app/ --set redis.enabled=true
# 6. Verify Redis pods
kubectl get pods -l app.kubernetes.io/instance=my-nginx
# Cleanup
helm uninstall my-nginx
12. Linting, Dry-Run, and Debugging¶
Use Helm’s built-in tools to validate, debug, and troubleshoot your chart before deploying.
Scenario:¶
◦ You modified several templates and want to catch errors before deploying to the cluster. ◦ Helm provides lint, template, dry-run, and debug tools for this purpose.
Hint: helm lint, helm template, --dry-run, --debug
Solution
# 1. Lint - checks for common errors and best practices
helm lint my-nginx-app/
# With values overrides
helm lint my-nginx-app/ -f values-dev.yaml
# 2. Template - render manifests locally (no cluster needed)
helm template my-release my-nginx-app/ > rendered.yaml
cat rendered.yaml
# 3. Dry-run - simulates install against the cluster (validates with API server)
helm install my-nginx my-nginx-app/ --dry-run
# 4. Dry-run + Debug - shows rendered templates AND computed values
helm install my-nginx my-nginx-app/ --dry-run --debug
# 5. Get rendered templates for a deployed release
helm get manifest my-nginx
# 6. Get the computed values for a deployed release
helm get values my-nginx
# 7. Get all information about a release
helm get all my-nginx
13. Package and Host a Chart Repository¶
Package the chart and host it in a Git-based chart repository using GitHub Pages.
Scenario:¶
◦ You want to share your Helm chart with your team or the community. ◦ A Helm chart repository is simply a web server hosting index.yaml and .tgz chart packages. ◦ GitHub Pages is a free and easy way to host a chart repo.
Hint: helm package, helm repo index, GitHub Pages
Solution
# ── Step 1: Package the chart ──
helm package my-nginx-app/
# Output: my-nginx-app-0.1.0.tgz
# ── Step 2: Create a chart repository on GitHub ──
# Create a new GitHub repository (e.g., "helm-charts")
# Clone it locally:
git clone https://github.com/<your-username>/helm-charts.git
cd helm-charts
# Create a docs/ directory (GitHub Pages will serve from here)
mkdir -p docs
# Move the packaged chart
cp ../my-nginx-app-0.1.0.tgz docs/
# ── Step 3: Generate the repository index ──
helm repo index docs/ --url https://<your-username>.github.io/helm-charts/
# Verify the index
cat docs/index.yaml
# ── Step 4: Push to GitHub ──
git add .
git commit -m "Add my-nginx-app chart"
git push origin main
# ── Step 5: Enable GitHub Pages ──
# Go to: GitHub repo → Settings → Pages
# Set Source: Deploy from branch → main → /docs
# Save and wait for deployment
# ── Step 6: Add the repo to Helm ──
helm repo add my-charts https://<your-username>.github.io/helm-charts/
helm repo update
# Verify the chart is available
helm search repo my-charts
# ── Step 7: Install from the repository ──
helm install my-nginx my-charts/my-nginx-app
Alternative: Use OCI Registry (Helm 3.8+)
14. Upgrade, Rollback, and Release History¶
Upgrade a release with new values, inspect its history, and rollback to a previous revision.
Scenario:¶
◦ You deployed version 1 of your chart, then upgraded to version 2 with bad configuration. ◦ You need to quickly rollback to the known-good state.
Hint: helm upgrade, helm history, helm rollback
Solution
# 1. Initial install (revision 1)
helm install my-nginx my-nginx-app/ \
--set welcomePage.title="Version 1"
# 2. Upgrade to revision 2 (change the title)
helm upgrade my-nginx my-nginx-app/ \
--set welcomePage.title="Version 2 - BROKEN"
# 3. Check release history
helm history my-nginx
# Output:
# REVISION STATUS DESCRIPTION
# 1 superseded Install complete
# 2 deployed Upgrade complete
# 4. Rollback to revision 1
helm rollback my-nginx 1
# 5. Verify history (now shows 3 revisions)
helm history my-nginx
# Output:
# REVISION STATUS DESCRIPTION
# 1 superseded Install complete
# 2 superseded Upgrade complete
# 3 deployed Rollback to 1
# 6. Verify the running app shows "Version 1" again
kubectl port-forward svc/my-nginx-my-nginx-app 8080:80
curl http://localhost:8080 | grep "Version"
# Cleanup
helm uninstall my-nginx
15. Hooks (Pre-install / Post-install)¶
Create Helm hooks that run a Job before and after chart installation.
Scenario:¶
◦ You need to run a database migration before the app starts, or send a notification after deployment. ◦ Helm hooks let you run resources at specific points in the release lifecycle.
Hint: helm.sh/hook annotation, pre-install, post-install
Solution
Step 1: Create a pre-install hook [templates/pre-install-job.yaml]
cat > my-nginx-app/templates/pre-install-job.yaml << 'TEMPLATE'
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-nginx-app.fullname" . }}-pre-install
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: pre-install
image: busybox
command:
- sh
- -c
- |
echo "=== Pre-install hook ==="
echo "Running pre-flight checks..."
echo "Release: {{ .Release.Name }}"
echo "Namespace: {{ .Release.Namespace }}"
echo "Chart: {{ .Chart.Name }}-{{ .Chart.Version }}"
echo "Pre-install complete!"
TEMPLATE
Step 2: Create a post-install hook [templates/post-install-job.yaml]
cat > my-nginx-app/templates/post-install-job.yaml << 'TEMPLATE'
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-nginx-app.fullname" . }}-post-install
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: post-install
image: busybox
command:
- sh
- -c
- |
echo "=== Post-install hook ==="
echo "Deployment verified!"
echo "Release: {{ .Release.Name }}"
echo "Post-install complete!"
TEMPLATE
Step 3: Deploy and observe hooks
# Install and watch the hooks execute
helm install my-nginx my-nginx-app/
# Check jobs (hook jobs auto-delete on success due to hook-delete-policy)
kubectl get jobs
# If you want to see the logs, remove the hook-delete-policy temporarily
# and check:
kubectl logs job/my-nginx-my-nginx-app-pre-install
kubectl logs job/my-nginx-my-nginx-app-post-install
# Hook execution order:
# 1. pre-install Job runs
# 2. Chart resources are created (Deployment, Service, etc.)
# 3. post-install Job runs
# Cleanup
helm uninstall my-nginx
Diagram: Helm Chart Architecture¶
┌──────────────────────────────────────────────────────────────┐
│ Helm Chart: my-nginx-app │
├──────────────────────────────────────────────────────────────┤
│ │
│ Chart.yaml ──── name, version, dependencies │
│ │
│ values.yaml ─── defaults ◄── values-dev.yaml │
│ ◄── values-prod.yaml │
│ ◄── --set overrides │
│ │
│ templates/ ─┬── deployment.yaml ──► Deployment │
│ ├── service.yaml ─────► Service (ClusterIP) │
│ ├── service-external ─► Service (ExternalName) │
│ ├── ingress-main ─────► Ingress (path: /) │
│ ├── ingress-status ───► Ingress (path: /status) │
│ ├── configmap-html ───► ConfigMap (welcome page)│
│ ├── pre-install-job ──► Hook (pre-install) │
│ ├── post-install-job ─► Hook (post-install) │
│ ├── _helpers.tpl ─────► Named templates │
│ └── NOTES.txt ────────► Post-install message │
│ │
│ charts/ ────── redis (subchart dependency) │
│ │
└──────────────────────────────────────────────────────────────┘
│
helm package ──► my-nginx-app-0.1.0.tgz
│
GitHub Pages / OCI Registry
│
helm repo add / helm install
Quick Reference: Essential Helm Commands¶
| Command | Description |
|---|---|
helm create <name> | Scaffold a new chart |
helm install <release> <chart> | Install a chart |
helm upgrade <release> <chart> | Upgrade a release |
helm upgrade --install | Install or upgrade (idempotent) |
helm uninstall <release> | Remove a release |
helm list | List installed releases |
helm history <release> | Show release revision history |
helm rollback <release> <rev> | Rollback to a previous revision |
helm template <release> <chart> | Render templates locally |
helm lint <chart> | Check chart for errors |
helm package <chart> | Package chart into .tgz |
helm repo index <dir> | Generate repository index |
helm repo add <name> <url> | Add a chart repository |
helm search repo <keyword> | Search charts in added repos |
helm dependency update <chart> | Download chart dependencies |
helm get values <release> | Show computed values for a release |
helm get manifest <release> | Show rendered manifests for a release |
16. Use helm status to Inspect a Release¶
Use helm status to view detailed information about a deployed release including resource status and NOTES.
Scenario:¶
◦ A release was deployed by another team member and you need to understand its current state. ◦ You want to see the NOTES.txt output again without reinstalling. ◦ helm status provides a quick overview of the release deployment status and health.
Hint: helm status, --revision, -o yaml
Solution
# 1. Install a release first
helm install my-nginx my-nginx-app/
# 2. Get the status of the release
helm status my-nginx
# Output shows:
# - Last deployment time
# - Release status (deployed, failed, pending, etc.)
# - Deployed resources
# - NOTES.txt content
# 3. Get status in YAML format
helm status my-nginx -o yaml
# 4. Get status in JSON format
helm status my-nginx -o json
# 5. Get status of a specific revision
helm status my-nginx --revision 1
# 6. Check status from a specific namespace
helm status my-nginx --namespace production
# Cleanup
helm uninstall my-nginx
17. Extract Values with helm get values¶
Use helm get values to see what values were actually used for a deployed release.
Scenario:¶
◦ You deployed a chart months ago with custom values and need to remember what overrides were applied. ◦ Multiple team members have upgraded the release and you want to know the current configuration. ◦ You need to replicate the same configuration in another environment.
Hint: helm get values, --all, --revision
Solution
# 1. Install with custom values
helm install my-nginx my-nginx-app/ \
--set replicaCount=3 \
--set welcomePage.title="Production App"
# 2. Get only the user-supplied values
helm get values my-nginx
# Output shows only the overrides:
# replicaCount: 3
# welcomePage:
# title: Production App
# 3. Get ALL values (including defaults from values.yaml)
helm get values my-nginx --all
# 4. Get values from a specific revision
helm upgrade my-nginx my-nginx-app/ --set replicaCount=5
helm get values my-nginx --revision 1
helm get values my-nginx --revision 2
# 5. Output as JSON
helm get values my-nginx -o json
# 6. Save values to file for reuse
helm get values my-nginx > my-nginx-values.yaml
# 7. Use saved values in another deployment
helm install my-nginx-copy my-nginx-app/ -f my-nginx-values.yaml
# Cleanup
helm uninstall my-nginx my-nginx-copy
18. Show Chart Values with helm show values¶
Use helm show values to inspect the default values of a chart before installing.
Scenario:¶
◦ You want to install a third-party chart from a repository but need to understand what configuration options are available. ◦ You’re evaluating multiple charts and want to compare their configuration interfaces. ◦ You need to create a custom values file but want to start from the defaults.
Hint: helm show values, chart repositories
Solution
# 1. Show default values of a local chart
helm show values my-nginx-app/
# 2. Show values from a packaged chart
helm package my-nginx-app/
helm show values my-nginx-app-0.1.0.tgz
# 3. Add a public chart repository
helm repo add bitnami https://charts.bitnami.com/bitnami
# 4. Show default values from a repository chart
helm show values bitnami/nginx
# 5. Show values at a specific chart version
helm show values bitnami/nginx --version 15.0.0
# 6. Save default values to file for customization
helm show values bitnami/nginx > nginx-defaults.yaml
# 7. Compare values between chart versions
helm show values bitnami/nginx --version 14.0.0 > nginx-v14-values.yaml
helm show values bitnami/nginx --version 15.0.0 > nginx-v15-values.yaml
diff nginx-v14-values.yaml nginx-v15-values.yaml
# 8. Show all chart information (Chart.yaml + README + values)
helm show all bitnami/nginx
helm show chart bitnami/nginx
helm show readme bitnami/nginx
19. Search Charts with helm search repo¶
Use helm search repo to find charts in added repositories.
Scenario:¶
◦ You need to deploy PostgreSQL but don’t want to write manifests from scratch. ◦ You want to find and compare available charts for a specific technology. ◦ You need to discover what version of a chart is available.
Hint: helm search repo, --versions, --version
Solution
# 1. Add popular chart repositories
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable https://charts.helm.sh/stable
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
# 2. Update repository index
helm repo update
# 3. Search for charts by keyword
helm search repo nginx
# 4. Search showing all available versions
helm search repo nginx --versions
# 5. Search with version constraint
helm search repo nginx --version "~15.0"
# 6. Search for development/pre-release versions
helm search repo nginx --devel
# 7. Search with regex pattern
helm search repo 'nginx.*'
# 8. Search across all repositories with output formatting
helm search repo postgresql -o json
helm search repo postgresql -o yaml
# 9. Search and filter with grep
helm search repo database | grep -i postgres
# 10. List all charts from a specific repository
helm search repo bitnami/
# 11. Show detailed output
helm search repo nginx --max-col-width 0
# 12. Install a found chart
helm search repo bitnami/redis --versions | head -5
helm install my-redis bitnami/redis --version 18.0.0
helm uninstall my-redis
20. Update Repositories with helm repo update¶
Use helm repo update to refresh the local cache of chart information.
Scenario:¶
◦ A new version of a chart was released but helm search doesn’t show it. ◦ You haven’t updated your repository index in weeks and want the latest charts. ◦ Similar to apt update or yum update, you need to sync the latest metadata.
Hint: helm repo update, helm repo list
Solution
# 1. List all configured repositories
helm repo list
# 2. Update all repositories
helm repo update
# Output shows each repository being refreshed:
# Hang tight while we grab the latest from your chart repositories...
# ...Successfully got an update from the "bitnami" chart repository
# ...Successfully got an update from the "stable" chart repository
# Update Complete.
# 3. Update a specific repository
helm repo update bitnami
# 4. Update multiple specific repositories
helm repo update bitnami stable
# 5. Force update even if repository fails
helm repo update --fail-on-repo-update-fail=false
# 6. Verify you can now see newer chart versions
helm search repo bitnami/nginx --versions | head -5
# 7. Typical workflow: update before searching or installing
helm repo update
helm search repo redis
helm install my-redis bitnami/redis
# Cleanup
helm uninstall my-redis
21. Run Chart Tests with helm test¶
Use helm test to run tests defined in the chart’s templates/tests/ directory.
Scenario:¶
◦ You deployed a release and want to verify it’s actually working correctly. ◦ The chart includes test pods that validate connectivity, configuration, or functionality. ◦ You want to include release validation in your CI/CD pipeline.
Hint: helm test, templates/tests/, helm.sh/hook: test
Solution
# 1. Create a test template if not already present
cat > my-nginx-app/templates/tests/test-connection.yaml << 'TEMPLATE'
apiVersion: v1
kind: Pod
metadata:
name: {{ include "my-nginx-app.fullname" . }}-test-connection
labels:
{{- include "my-nginx-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "my-nginx-app.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
TEMPLATE
# 2. Install the release
helm install my-nginx my-nginx-app/
# 3. Run the tests
helm test my-nginx
# Output shows:
# NAME: my-nginx
# ...
# Phase: Succeeded
# 4. Run tests with logs displayed
helm test my-nginx --logs
# 5. Run tests with timeout
helm test my-nginx --timeout 2m
# 6. View test pod logs manually
kubectl logs my-nginx-my-nginx-app-test-connection
# 7. Filter which tests to run (if multiple tests exist)
helm test my-nginx --filter name=test-connection
# 8. Clean up test pods after running (by default they remain)
kubectl delete pod -l 'helm.sh/hook=test'
# Or configure it in the test template with:
# "helm.sh/hook-delete-policy": "hook-succeeded,hook-failed"
# Cleanup
helm uninstall my-nginx
22. Use helm get all to Retrieve Complete Release Info¶
Use helm get all to retrieve all information about a deployed release in one command.
Scenario:¶
◦ You need to debug why a release isn’t working correctly. ◦ You want to export the complete release configuration for documentation or backup. ◦ You need to see the rendered manifests, computed values, and hooks all together.
Hint: helm get all, helm get manifest, helm get hooks
Solution
# 1. Install a release
helm install my-nginx my-nginx-app/ \
--set replicaCount=2 \
--set welcomePage.title="Debug Test"
# 2. Get all information about the release
helm get all my-nginx
# Output includes:
# - Release metadata
# - User-supplied values
# - Computed values
# - Rendered Kubernetes manifests
# - Hooks
# - Notes
# 3. Get all info from specific revision
helm upgrade my-nginx my-nginx-app/ --set replicaCount=3
helm get all my-nginx --revision 1
helm get all my-nginx --revision 2
# 4. Get individual components
helm get manifest my-nginx # Just the rendered manifests
helm get values my-nginx # Just the user values
helm get hooks my-nginx # Just the hooks
helm get notes my-nginx # Just the NOTES.txt
# 5. Export to file for backup/documentation
helm get all my-nginx > my-nginx-release-backup.yaml
# 6. Use template to extract specific information
helm get all my-nginx --template '{{.Release.Manifest}}'
# 7. Compare two revisions
helm get all my-nginx --revision 1 > rev1.yaml
helm get all my-nginx --revision 2 > rev2.yaml
diff rev1.yaml rev2.yaml
# Cleanup
helm uninstall my-nginx
rm -f rev1.yaml rev2.yaml my-nginx-release-backup.yaml
23. Use helm list with Filters and Formatting¶
Master helm list with various filters and output formats to manage multiple releases.
Scenario:¶
◦ You have dozens of releases across multiple namespaces and need to find specific ones. ◦ You want to script release management and need machine-readable output. ◦ You need to filter releases by status (deployed, failed, pending).
Hint: helm list, --all-namespaces, --filter, -o json
Solution
# 1. Install multiple releases for testing
helm install nginx-dev my-nginx-app/ --set replicaCount=1
helm install nginx-staging my-nginx-app/ --set replicaCount=2
helm install nginx-prod my-nginx-app/ --set replicaCount=3 --namespace prod --create-namespace
# 2. List all releases in current namespace
helm list
# 3. List releases across all namespaces
helm list --all-namespaces
# 4. List releases in specific namespace
helm list --namespace prod
# 5. Filter releases by name pattern
helm list --filter 'nginx-.*'
helm list --filter 'nginx-dev'
# 6. Show only deployed releases
helm list --deployed
# 7. Show all releases including uninstalled (with --keep-history)
helm list --all
# 8. Show failed releases
helm list --failed
# 9. Show pending releases
helm list --pending
# 10. Output as JSON (for scripting)
helm list -o json
# 11. Output as YAML
helm list -o yaml
# 12. Show extended information
helm list --all-namespaces -o wide
# 13. Limit number of results
helm list --max 5
# 14. Sort by date
helm list --date
# 15. Reverse sort order
helm list --reverse
# 16. Show specific columns only (use with jq for JSON output)
helm list -o json | jq '.[] | {name: .name, status: .status, namespace: .namespace}'
# 17. Count releases
helm list --all-namespaces | wc -l
# 18. Find releases using specific chart
helm list --all-namespaces -o json | jq '.[] | select(.chart | contains("my-nginx-app"))'
# Cleanup
helm uninstall nginx-dev nginx-staging
helm uninstall nginx-prod -n prod
kubectl delete namespace prod
24. Chain Multiple Commands for Release Management¶
Practice chaining Helm commands for common workflows and debugging scenarios.
Scenario:¶
◦ You need to quickly deploy, verify, and troubleshoot releases in rapid iteration cycles. ◦ You want to create reusable scripts for release management. ◦ You need to validate deployments in CI/CD pipelines.
Hint: Combine install, status, get values, test, upgrade, rollback
Solution
# ── Workflow 1: Install, verify, test ──
helm install my-nginx my-nginx-app/ && \
helm status my-nginx && \
helm test my-nginx
# ── Workflow 2: Dry-run, lint, then install ──
helm lint my-nginx-app/ && \
helm install my-nginx my-nginx-app/ --dry-run --debug && \
helm install my-nginx my-nginx-app/
# ── Workflow 3: Template, validate, install ──
helm template my-nginx my-nginx-app/ | kubectl apply --dry-run=client -f - && \
helm install my-nginx my-nginx-app/
# ── Workflow 4: Upgrade or install (idempotent) ──
helm upgrade --install my-nginx my-nginx-app/ --wait --timeout 5m
# ── Workflow 5: Upgrade with backup and rollback on failure ──
helm get values my-nginx > backup-values.yaml && \
helm upgrade my-nginx my-nginx-app/ --set replicaCount=5 --atomic
# ── Workflow 6: Install, check status, get all info ──
helm install my-nginx my-nginx-app/ && \
sleep 10 && \
helm status my-nginx && \
helm get all my-nginx
# ── Workflow 7: Compare before and after upgrade ──
helm get values my-nginx > before.yaml && \
helm upgrade my-nginx my-nginx-app/ --set newKey=newValue && \
helm get values my-nginx > after.yaml && \
diff before.yaml after.yaml
# ── Workflow 8: Install with custom values and verify ──
cat > custom.yaml << EOF
replicaCount: 3
welcomePage:
title: "Production"
EOF
helm install my-nginx my-nginx-app/ -f custom.yaml && \
kubectl get pods -l app.kubernetes.io/instance=my-nginx
# ── Workflow 9: Rollback if tests fail ──
helm upgrade my-nginx my-nginx-app/ --set replicaCount=10 && \
helm test my-nginx || helm rollback my-nginx
# ── Workflow 10: Clean reinstall ──
helm uninstall my-nginx 2>/dev/null || true && \
helm install my-nginx my-nginx-app/ --wait
# ── Workflow 11: Multi-environment deployment ──
for env in dev staging prod; do
helm upgrade --install my-nginx-$env my-nginx-app/ \
-f values-$env.yaml \
--namespace $env --create-namespace
done
# List all deployments
helm list --all-namespaces
# ── Workflow 12: Debugging failed release ──
helm status my-nginx && \
helm get values my-nginx --all && \
helm get manifest my-nginx | kubectl apply --dry-run=client -f - && \
kubectl describe pods -l app.kubernetes.io/instance=my-nginx
# Cleanup all
for env in dev staging prod; do
helm uninstall my-nginx-$env -n $env 2>/dev/null || true
kubectl delete namespace $env 2>/dev/null || true
done
helm uninstall my-nginx 2>/dev/null || true
rm -f backup-values.yaml before.yaml after.yaml custom.yaml