Skip to content

Advanced Helm Charts — Hooks, Tests, Dependencies, and Production Patterns

DodaTech Updated 2026-06-22 8 min read

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
â„šī¸ Info

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

  1. Hook Jobs not completing before upgrade proceeds: By default, Helm waits for Jobs to complete only if --wait is set. Without --wait, Helm creates the hook Job and immediately proceeds with the upgrade, potentially deploying the new app version before the Migration finishes.

  2. 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 update to resolve conflicts and ensure compatible versions.

  3. Test Pods leaving resources behind: Test Pods are not automatically deleted. After helm test, Pods remain in Completed state. Use helm test --filter to run specific tests and manually clean up with kubectl delete pod -l helm.sh/hook=test.

  4. Values not propagating to subcharts: Subchart values must be nested under the subchart name in values.yaml. A value api.replicas: 5 in the parent chart automatically propagates to the api subchart. Global values use the global: key.

  5. 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

  1. 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.

  2. How do chart tests validate a release? Answer: Chart tests are Pods annotated with helm.sh/hook: test that run during helm 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.

  3. What is the purpose of helm.sh/hook-delete-policy? Answer: This annotation controls when the hook resource is deleted. Options include before-hook-creation (delete existing before creating new), hook-succeeded (delete after success), and hook-failed (delete after failure). This prevents accumulation of completed hook Jobs.

  4. How do global values work in umbrella charts? Answer: Values defined under the global: key in the parent chart's values.yaml are 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.

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