Advanced Helm Charts â Hooks, Tests, Dependencies, and Production Patterns
In this tutorial, you'll learn about Advanced Helm Charts. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Advanced Helm charts use hooks for lifecycle management, automated tests for validation, subchart dependencies for modularity, and conditional templates to handle multiple environments from a single chart definition.
What You'll Learn
Why It Matters
Basic Helm charts work for simple applications but fail in production scenarios that require ordered operations (migrate a database before deploying the new app version), component reuse across teams, environment-specific configuration without duplication, and automated validation that a release was successful. Advanced patterns solve these problems while keeping charts maintainable as they grow to hundreds of templates.
Real-World Use
DodaTech packages Durga Antivirus Pro as an umbrella chart with 5 subcharts (api, worker, scheduler, Migration, ingress). The Migration subchart runs as a pre-upgrade hook, and the test suite validates all endpoints are healthy after each release. A single helm test command confirms the deployment is functional.
flowchart TD
A["Helm Install / Upgrade"] --> B{"Hooks"}
B --> C["pre-install: create CRDs"]
B --> D["pre-upgrade: run DB migration"]
B --> E["post-install: notify Slack"]
B --> F["post-upgrade: run tests"]
F --> G["Helm Test: Pods running"]
F --> H["Helm Test: API responding"]
F --> I["Helm Test: DB connection"]
G --> J{"All tests pass?"}
H --> J
I --> J
J -->|"Yes"| K["Release successful"]
J -->|"No"| L["Rollback"]
L --> M["helm rollback 1"]
style A fill:#0F1689,color:#fff
style F fill:#326CE5,color:#fff
style J fill:#269539,color:#fff
style L fill:#CC3333,color:#fff
Prerequisites: Working knowledge of Helm Charts (chart structure, values, templates), Kubernetes cluster, and Helm CLI 3.x installed.
Helm Hooks
Hooks run at specific points in the release lifecycle. They are regular Kubernetes manifests with a Helm annotation that tells Helm when to apply them.
# templates/migration-hook.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-db-migration
annotations:
helm.sh/hook: pre-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["node", "migrate.js"]
env:
- name: DATABASE_URL
value: {{ .Values.database.url }}
Expected behavior: When helm upgrade is executed, Helm first creates the Migration Job. Only after the Job completes successfully does Helm proceed with the upgrade. If the Job fails, the upgrade is aborted.
# templates/post-install-notify.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-notify
annotations:
helm.sh/hook: post-install
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: notify
image: curlimages/curl:8.5.0
command:
- curl
- -X
- POST
- -H
- "Content-Type: application/json"
- -d
- '{"text":"Deployment {{ .Release.Name }} v{{ .Values.image.tag }} deployed to {{ .Values.environment }}"}'
- {{ .Values.slackWebhook }}
| Hook Type | When It Runs | Typical Use |
|---|---|---|
| pre-install | After template render, before install | Create CRDs, initialize resources |
| post-install | After install succeeds | Notifications, logging |
| pre-upgrade | Before upgrade manifests are applied | Database migrations, backup |
| post-upgrade | After upgrade succeeds | Validation tests, cache warming |
| pre-rollback | Before rollback | Pre-rollback snapshots |
| post-rollback | After rollback succeeds | Cleanup, notifications |
| test | During helm test |
Functional validation |
Chart Tests
Tests are Pods that run during helm test to verify the release is functioning correctly.
# templates/test-api-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: {{ .Release.Name }}-api-test
annotations:
helm.sh/hook: test
spec:
containers:
- name: test
image: curlimages/curl:8.5.0
command:
- sh
- -c
- |
curl -f http://{{ .Release.Name }}-api:{{ .Values.api.port }}/healthz || exit 1
curl -f http://{{ .Release.Name }}-api:{{ .Values.api.port }}/ready || exit 1
echo "All endpoints healthy"
restartPolicy: Never
# templates/test-db-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: {{ .Release.Name }}-db-test
annotations:
helm.sh/hook: test
spec:
containers:
- name: test
image: postgres:16-alpine
command:
- sh
- -c
- |
pg_isready -h {{ .Release.Name }}-postgresql -U {{ .Values.database.user }}
echo "Database connection successful"
restartPolicy: Never
Expected behavior: Running helm test <release> creates these Pods. If both Pods exit with code 0, the test passes. If either fails, helm test returns a non-zero exit code and shows the failing Pod logs.
# Run chart tests
helm test my-release --logs
# Expected output:
# Pod my-release-api-test pending
# Pod my-release-api-test succeeded
# Pod my-release-db-test succeeded
# + echo 'All endpoints healthy'
# All endpoints healthy
# + pg_isready -h my-release-postgresql -U app
# /var/run/postgresql:5432 - accepting connections
# + echo 'Database connection successful'
# Database connection successful
Dependency Management with Subcharts
Umbrella charts combine multiple subcharts into one deployable unit. Dependencies are declared in Chart.yaml.
# Chart.yaml (umbrella chart)
apiVersion: v2
name: durga-platform
description: Durga Antivirus Pro umbrella chart
version: 2.0.0
appVersion: "4.5.0"
dependencies:
- name: postgresql
version: "~12.1.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "~18.0.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
- name: api
version: ">=1.0.0"
repository: "file://./charts/api"
- name: worker
version: ">=1.0.0"
repository: "file://./charts/worker"
# values.yaml (umbrella override)
api:
image:
repository: dodatech/api
tag: "4.5.0"
replicas: 3
worker:
image:
repository: dodatech/worker
tag: "4.5.0"
queueDepth: 100
postgresql:
enabled: true
auth:
database: durga
username: app
primary:
persistence:
size: 100Gi
redis:
enabled: true
architecture: standalone
# Build dependencies
helm dependency update
# Expected output:
# Saving 4 charts
# Downloading postgresql from https://charts.bitnami.com/bitnami
# Downloading redis from https://charts.bitnami.com/bitnami
# Dependency api from file://./charts/api
# Dependency worker from file://./charts/worker
# Deploy everything with one command
helm install durga-platform . --namespace production --create-namespace
# Expected output:
# NAME: durga-platform
# LAST DEPLOYED: ...
# NAMESPACE: production
# STATUS: deployed
# This chart deploys 4 components: postgresql, redis, api, worker
Conditional Templates and Named Templates
Use _helpers.tpl to define reusable template functions and if blocks for conditional resource creation.
# templates/_helpers.tpl
{{- define "durga.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{- define "durga.image" -}}
{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
{{- end -}}
{{- define "durga.ingress.enabled" -}}
{{- if and .Values.ingress.enabled .Values.ingress.host -}}
true
{{- else -}}
false
{{- end -}}
{{- end -}}
# templates/ingress.yaml
{{- if eq (include "durga.ingress.enabled" .) "true" }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}-ingress
labels:
{{- include "durga.labels" . | nindent 4 }}
spec:
ingressClassName: {{ .Values.ingress.className }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}-api
port:
number: {{ .Values.api.port }}
{{- end }}
Common Errors
Hook Jobs not completing before upgrade proceeds: By default, Helm waits for Jobs to complete only if
--waitis set. Without--wait, Helm creates the hook Job and immediately proceeds with the upgrade, potentially deploying the new app version before the Migration finishes.Subchart version conflicts: Different subcharts may depend on different versions of the same chart (e.g., two subcharts depending on different PostgreSQL versions). Use
helm dependency updateto resolve conflicts and ensure compatible versions.Test Pods leaving resources behind: Test Pods are not automatically deleted. After
helm test, Pods remain inCompletedstate. Usehelm test --filterto run specific tests and manually clean up withkubectl delete pod -l helm.sh/hook=test.Values not propagating to subcharts: Subchart values must be nested under the subchart name in
values.yaml. A valueapi.replicas: 5in the parent chart automatically propagates to theapisubchart. Global values use theglobal:key.Weight conflicts in hook ordering: Hooks with the same weight run in parallel. If a pre-upgrade backup Job and a pre-upgrade Migration Job both have weight 1, they run simultaneously and the Migration might start before the backup finishes. Assign different weights to enforce ordering.
Practice Questions
What is the difference between a Helm hook and a regular template resource? Answer: A regular template resource is created when the release is installed or upgraded and is managed by Helm throughout the release lifecycle. A hook resource runs at a specific lifecycle point (pre/post install/upgrade/rollback) and is not managed as part of the release -- it is deleted after execution depending on the delete policy.
How do chart tests validate a release? Answer: Chart tests are Pods annotated with
helm.sh/hook: testthat run duringhelm test. They validate the release by checking that endpoints respond, databases accept connections, and other functional criteria pass. They must exit with code 0 to pass.What is the purpose of
helm.sh/hook-delete-policy? Answer: This annotation controls when the hook resource is deleted. Options includebefore-hook-creation(delete existing before creating new),hook-succeeded(delete after success), andhook-failed(delete after failure). This prevents accumulation of completed hook Jobs.How do global values work in umbrella charts? Answer: Values defined under the
global:key in the parent chart'svalues.yamlare accessible in all subcharts as.Values.global.*. This is useful for sharing common configuration like image registries, domain names, or feature flags across all components.
Challenge
Create an umbrella chart for a three-tier application with these requirements: a PostgreSQL subchart from Bitnami, an API subchart (local file dependency), a worker subchart (local file dependency), a pre-upgrade hook that runs database migrations, a post-upgrade hook that clears the Redis cache, chart tests that verify API health and database connectivity, conditional Ingress creation (only when ingress.enabled: true), and global values for the image registry and environment name. Package the chart and run helm install followed by helm test.
Mini Project
Build a production-ready Helm chart for a microservice with all advanced patterns: define named templates in _helpers.tpl for labels, annotations, and image references, create a pre-install hook that initializes database schemas, create a pre-upgrade hook that backs up the database and runs migrations, create post-install/post-upgrade hooks that send Slack notifications, write 3 chart tests (HTTP health, database connection, Redis ping), add a Redis subchart dependency with conditional enablement, use global values for the environment label and monitoring annotations, implement conditional sidecar injection for a logging agent, document the chart in a README with usage examples, and publish the chart to a Helm Repository.
Related Resources
| Resource | Description |
|---|---|
| Helm Basics | Foundation chart concepts |
| Kubernetes Deployments | Underlying resources |
| GitOps with ArgoCD | Managing Helm releases via GitOps |
| CI/CD Pipelines | Automating chart deployment |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro