Helm Charts: Templating, Values, and Package Management

Helm Charts guide covering templates, values management, chart repositories, and production deployment workflows.

published: reading time: 29 min read author: GeekWorkBench

Helm Charts: Templating, Values, and Kubernetes Package Management

Helm is the package manager for Kubernetes. Managing multiple YAML files across different environments gets tedious fast. Helm solves this by letting you package everything into a chart that you can version, share, and deploy with a single command.

This guide covers creating basic charts through advanced templating patterns. If you are new to Kubernetes containers, start with our Docker Fundamentals and Advanced Kubernetes guides first.

Introduction

Helm has a client-server architecture. The Helm client interacts with the server-side component called Tiller in Helm 2, or uses the cluster’s service account in Helm 3.

graph LR
    A[Helm Client] -->|Chart| B[Chart Repository]
    A -->|Kubeconfig| C[Kubernetes Cluster]
    C -->|Release| D[Release History]
    C -->|Resources| E[Deployed Resources]

Helm 3 removed Tiller and introduced cluster-scoped service accounts with release-level scope. This was a significant improvement for production use.

Chart Structure

A chart is a collection of files organized in a specific directory structure:

my-chart/
  Chart.yaml          # Chart metadata
  values.yaml         # Default configuration values
  values.schema.json   # Optional JSON schema validation
  charts/              # Dependency charts (Helm 3 style)
  templates/           # Kubernetes manifest templates
  templates/NOTES.txt  # Post-install notes
  .helmignore          # Files to ignore during packaging

Chart.yaml

The Chart.yaml contains the chart’s metadata:

apiVersion: v2
name: my-application
description: A Helm chart for my production application
type: application
version: 1.2.3
appVersion: "2.0.0"
kubeVersion: ">=1.24.0"
keywords:
  - web application
  - api
home: https://github.com/example/my-app
sources:
  - https://github.com/example/my-app
maintainers:
  - name: DevOps Team
    email: devops@example.com
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com"
    condition: postgresql.enabled
  - name: redis
    version: "17.x.x"
    repository: "https://charts.bitnami.com"
    condition: redis.enabled

The apiVersion: v2 format is for Helm 3. Helm 2 used apiVersion: v1.

Values Files

Values files provide configuration that gets merged into templates. Helm uses a cascading values system: default values in values.yaml, overridden by environment-specific files, overridden by command-line flags.

values.yaml

# Default configuration
replicaCount: 3

image:
  repository: myregistry/myapp
  pullPolicy: IfNotPresent
  tag: "1.0.0"

service:
  type: ClusterIP
  port: 8080

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: api.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: myapp-tls
      hosts:
        - api.example.com

resources:
  limits:
    cpu: 1000m
    memory: 1Gi
  requests:
    cpu: 100m
    memory: 256Mi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

postgresql:
  enabled: true
  auth:
    database: myapp
    username: myapp
  primary:
    persistence:
      size: 10Gi

redis:
  enabled: true
  auth:
    password: ""

Environment-Specific Values

Create environment-specific value files:

# values-staging.yaml
replicaCount: 2
image:
  tag: "1.0.0-staging"
resources:
  limits:
    cpu: 500m
    memory: 512Mi
autoscaling:
  enabled: false
ingress:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
  hosts:
    - host: staging-api.example.com
# values-prod.yaml
replicaCount: 5
image:
  tag: "1.0.0-production"
resources:
  limits:
    cpu: 2000m
    memory: 2Gi
autoscaling:
  minReplicas: 5
  maxReplicas: 20

Deploy with specific values:

helm upgrade --install myapp ./charts/myapp \
  --values values-prod.yaml \
  --namespace production \
  --create-namespace

Template Functions and Pipelines

Helm templates use Go template syntax with Sprig functions for manipulation.

Common Functions

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "myapp.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.port }}
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /health
              port: http
          readinessProbe:
            httpGet:
              path: /ready
              port: http
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}

String Functions

# Uppercase
appName: { { .Values.name | upper } }

# Lowercase
envVar: { { .Values.env | lower | squote } }

# Truncate
shortName: { { .Values.name | trunc 63 } }

# Replace
fixedName: { { .Values.name | replace "_" "-" } }

# Quote
quoted: { { .Values.name | quote } }

# Default value
tag: { { .Values.image.tag | default .Chart.AppVersion } }

Conditional Logic

# Ternary operator
replicas: {{ .Values.replicaCount | default 1 }}

# If-else blocks
{{- if .Values.ingress.enabled }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "myapp.fullname" . }}
spec:
  {{- with .Values.ingress }}
  ingressClassName: {{ .className }}
  {{- end }}
{{- end }}

Range Loops

# Loop over list
env:
  {{- range .Values.env }}
  - name: {{ .name }}
    value: {{ .value | quote }}
  {{- end }}

# Loop over key-value map
labels:
  {{- range $key, $value := .Values.labels }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}

# Loop with index
{{- range $i, $v := .Values.replicas }}
- name: replica-{{ $i }}
{{- end }}

Named Templates

Named templates (partials) live in templates/_helpers.tpl and you can include them throughout your chart.

Defining Helpers

# templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Full name including release and chart.
*/}}
{{- define "myapp.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}

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

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

Using Helpers in Templates

apiVersion: apps/v1
kind: Deployment
metadata:
  name: { { include "myapp.fullname" . } }
  labels: { { include "myapp.labels" . | nindent 4 } }
spec:
  replicas: { { .Values.replicaCount } }
  selector:
    matchLabels: { { include "myapp.selectorLabels" . | nindent 6 } }

Chart Dependencies

Charts can depend on other charts. In Helm 3, you define dependencies in Chart.yaml and download them to the charts/ directory.

Dependency Management

# Chart.yaml
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com"
    condition: postgresql.enabled
  - name: redis
    version: "17.x.x"
    repository: "https://charts.bitnami.com"
    condition: redis.enabled

Update dependencies:

helm dependency update ./charts/myapp

This creates a Chart.lock file that locks dependency versions.

Sub-chart Values

Parent charts can override sub-chart values:

# values.yaml
postgresql:
  primary:
    persistence:
      size: 50Gi
  auth:
    database: myapp_prod
    username: myapp

redis:
  auth:
    password: secretpassword
  master:
    persistence:
      enabled: false

Hooks

Hooks run at specific points in the release lifecycle. Use them for database migrations, backup jobs, or waiting for dependencies.

Common Hook Annotations

# hooks/post-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "myapp.fullname" . }}-migrations
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": post-install,post-upgrade
    "helm.sh/hook-weight": "-1"
    "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrations
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          command:
            - /app/migrate.sh

Hook Weights

Hooks execute in order of weight. Negative weights run first. This ensures database migrations run before your application starts.

Testing Charts

Helm includes a test framework for validating chart installations.

Test Pod

# templates/tests/pod-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: {{ include "myapp.fullname" . }}-test-connection
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test
    "helm.sh/hook-delete-policy": test-success
spec:
  restartPolicy: Never
  containers:
    - name: wget
      image: busybox:1.36
      command:
        - wget
      args:
        - '-O-'
        - 'http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health'

Run tests:

helm test myapp --namespace production

Chart Repositories

Chart repositories serve packaged charts via an HTTP server. You can create your own repository for internal charts.

Creating a Repository

# Package your chart
helm package ./charts/myapp

# Create index.yaml
helm repo index ./charts --url https://charts.example.com/

# Serve repository via HTTP
python3 -m http.server 8080 --directory ./charts

Adding and Using Repositories

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

# Update repositories
helm repo update

# Search for charts
helm search repo bitnami/postgresql

# Install from repository
helm install mydb bitnami/postgresql --values values.yaml

GitHub Pages Repository

Host your chart repository on GitHub Pages:

# In your charts repository
git checkout gh-pages || git checkout -b gh-pages

# Copy your chart package
cp ../my-chart-1.2.3.tgz ./

# Update index
helm repo index . --url https://example.github.io/charts

git add .
git commit -m "Add my-chart v1.2.3"
git push origin gh-pages

Library Charts

Library charts provide reusable templates that other charts can include. They are useful for standardizing common patterns across your organization.

Library Chart Structure

library-chart/
  Chart.yaml
  templates/
    _deployment.yaml
    _service.yaml
    _configmap.yaml
# Chart.yaml
apiVersion: v2
name: myapp-library
type: library
version: 1.0.0

Using Library Chart

# In your application chart
dependencies:
  - name: myapp-library
    version: "1.x.x"
    repository: "https://charts.example.com"
    import-values:
      - data

JSON Schema Validation

Values schema files validate values provided to your chart:

// values.schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "replicaCount": {
      "type": "integer",
      "minimum": 1,
      "maximum": 10
    },
    "image": {
      "type": "object",
      "properties": {
        "repository": { "type": "string" },
        "tag": { "type": "string" },
        "pullPolicy": {
          "type": "string",
          "enum": ["IfNotPresent", "Always", "Never"]
        }
      },
      "required": ["repository"]
    },
    "service": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": ["ClusterIP", "LoadBalancer", "NodePort"]
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        }
      },
      "required": ["type", "port"]
    }
  },
  "required": ["image", "service"]
}

If a user provides invalid configuration, Helm rejects it during template rendering:

helm template myapp ./charts/myapp --values invalid-values.yaml
# Error: values don't meet the schema

Best Practices

Production Deployment Checklist

Pin exact versions in dependencies. Use values.schema.json for validation. Keep values.yaml well-documented with comments. Use hooks for stateful operations like migrations. Test charts with helm template and helm test. Store secrets outside the chart and use external secrets tools.

Security Considerations

Do not embed secrets in values files. Use external secrets solutions like External Secrets Operator or HashiCorp Vault. Run containers as non-root. Scan charts for vulnerabilities with tools like Trivy or Snyk.

Release Management

Use CI/CD pipelines for chart releases. Maintain CHANGELOG.md for version history. Tag releases with Git tags matching chart versions. Sign charts with GPG for verification.

When to Use / When Not to Use

Helm is powerful but not always the right choice. Here is when to use it and when to consider alternatives.

When to Use Helm

Use Helm when:

  • You deploy applications to Kubernetes across multiple environments (dev, staging, production)
  • You need to manage complex applications with many Kubernetes resources
  • You want to version and roll back application configurations
  • You are sharing infrastructure or platform components across teams
  • You need to package applications for distribution via chart repositories
  • Your application has environment-specific configuration that changes between deployments

Use Helm for stateful applications when:

  • The application has clear upgrade paths and rollback procedures
  • You have tested hooks for database migrations or similar operations
  • Storage and secrets are properly externalized from the chart

When Not to Use Helm

Consider alternatives when:

  • Simple one-off deployments: kubectl apply -f may suffice
  • GitOps workflows: Tools like ArgoCD or Flux have built-in templating and might integrate better
  • Extremely dynamic workloads: Helm’s release model assumes relatively stable configurations
  • Strict immutability requirements: Helm’s upgrade pattern modifies releases in place
  • Security-restricted environments: Tiller in Helm 2 required elevated permissions (Helm 3 addressed most concerns)

Helm vs Alternatives

ToolBest ForLimitations
HelmApplication packaging, multi-environment deploymentsTemplate complexity for very dynamic workloads
KustomizeOverlay-based configuration, Git-native workflowsLess structured than Helm charts
ArgoCDGitOps, declarative continuous deliveryRequires additional setup for templating
raw kubectlOne-off deployments, simple resourcesNo parameterization, no rollback support

Production Failure Scenarios

Helm failures in production can block deployments or cause unexpected behavior.

FailureImpactMitigation
Hook failureRelease marked as failed, subsequent hooks do not runDesign hooks to be idempotent, use hook-delete-policy
Template rendering errorDeployment fails silently with wrong configurationUse --dry-run in CI, validate with JSON schema
Dependency unavailableChart fails to install or upgradePin exact versions in Chart.lock, maintain mirror
Release name collisionCannot install or upgrade without forceUse namespaces for isolation, unique naming conventions
Stuck release (pending-upgrade)Cannot upgrade or rollbackUse helm rollback or manually remove finalizers
Upgrade causes data lossStateful workloads may lose dataAlways test with dry-run first, backup before upgrade
Image pull failuresPods hang in Pending stateUse private registries with image pull secrets
Insufficient cluster resourcesPods cannot be scheduledSet appropriate resource requests and limits

Stuck Release Recovery

# Identify stuck releases
helm list --all --failed

# Get release history
helm history myapp --namespace production

# Rollback to last working revision
helm rollback myapp --namespace production

# If rollback fails, manually remove the release
kubectl delete secret -n production -l "owner=helm,name=myapp"

Common Pitfalls / Anti-Patterns

Template Pitfalls

Overusing toYaml

# Anti-pattern: Unbounded toYaml
spec:
  {{- toYaml .Values.podSpec | nindent 8 }}

# Better: Explicitly define expected fields
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    {{- include "myapp.selectorLabels" . | nindent 6 }}

Not handling nil values

# Anti-pattern: Will fail if .Values.ingress is nil
annotations:
  {{- .Values.ingress.annotations | toYaml | nindent 8 }}

# Better: Use conditional checks
{{- with .Values.ingress }}
annotations:
  {{- .annotations | toYaml | nindent 8 }}
{{- end }}

Forgetting the minus sign for whitespace control

# Anti-pattern: Extra blank line before content
spec:
  containers:
    - name: app
      image: {{ .Values.image }}

# Better: Hyphen trims preceding whitespace
spec:
  containers:
    - name: app
      image: {{ .Values.image }}

Values Structure Pitfalls

Flat values that should be nested

# Anti-pattern: Flat structure makes templating complex
replicaCount: 3
imageRepository: myapp
imageTag: "1.0.0"
servicePort: 8080

# Better: Group related values
replicaCount: 3
image:
  repository: myapp
  tag: "1.0.0"
service:
  type: ClusterIP
  port: 8080

Not using JSON schema validation Without schema validation, users can pass any values causing cryptic template errors at render time.

Hook Pitfalls

Non-idempotent hooks

# Anti-pattern: Hook creates duplicate resources each run
metadata:
  annotations:
    "helm.sh/hook": post-upgrade
    "helm.sh/hook-weight": "1"
spec:
  containers:
    - name: migrate
      command: ["/app/migrate.sh"]
      # This runs every upgrade, creating duplicates if not cleaned up

Not handling hook failures gracefully

# Anti-pattern: Hook failure blocks entire release
annotations:
  "helm.sh/hook": post-upgrade
  "helm.sh/hook-failure-policy": fail

# Better: Allow failure without blocking
annotations:
  "helm.sh/hook": post-upgrade
  "helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation

Dependency Pitfalls

Not pinning versions

# Anti-pattern: Version range can cause unexpected behavior
dependencies:
  - name: postgresql
    version: ">=12.0.0"

# Better: Pin exact version for reproducibility
dependencies:
  - name: postgresql
    version: "12.8.0"

Circular dependencies Avoid charts that depend on each other. This causes installation and upgrade ordering issues.

Key Takeaways & Checklist

Key Takeaways

  • Helm charts package Kubernetes resources with templated values for environment-specific configuration
  • The cascading values system (default < environment file < CLI flags) enables flexible deployments
  • Named templates in _helpers.tpl promote consistency and reduce duplication
  • Hooks handle stateful operations like migrations but require careful design for idempotency
  • Library charts extract common patterns for reuse across multiple application charts
  • JSON schema validation prevents misconfiguration before template rendering

Production Readiness Checklist

# Template validation
helm template myapp ./charts/myapp --debug

# Dry-run installation
helm upgrade --install myapp ./charts/myapp \
  --dry-run --debug \
  --values values-prod.yaml \
  --namespace production

# Schema validation
helm lint ./charts/myapp --strict

# Test installation
helm test myapp --namespace production

# Dependency update and lock
helm dependency update ./charts/myapp
helm dependency build ./charts/myapp

# Verify rendered templates
helm get manifest myapp --namespace production

# Check release status
helm status myapp --namespace production
helm history myapp --namespace production

Pre-Production Validation Commands

# Scan for vulnerabilities
trivy image $(helm get values myapp -n production -o jsonpath='{.data.image}' | base64 -d)

# Verify RBAC permissions
kubectl auth can-i get pods --as=system:serviceaccount:production:helm-deployer -n production

# Check for deprecated APIs
helm template myapp ./charts/myapp | kubeval --strict

# Review rendered YAML differences
helm diff upgrade myapp ./charts/myapp --values values-prod.yaml -n production

Observability Checklist

Helm charts need observability at both the release and application level.

Release Monitoring

graph LR
    A[Helm Release] --> B[Release Metadata]
    A --> C[Revision History]
    A --> D[Resource Status]
    B --> E[Name Namespace]
    B --> F[Chart Version]
    C --> G[Upgrade Rollback]
    D --> H[Pod Health]
    D --> I[PVC Bound]

Track these release metrics:

  • Release revision count: too many revisions indicate instability
  • Time since last successful deployment
  • Number of failed releases across environments
  • Hook execution success rates

Application Observability

graph TD
    A[Application Metrics] --> B[Pod Resource Usage]
    A --> C[Service Endpoints]
    A --> D[Ingress Status]
    E[Kubernetes Events] --> F[Pod Scheduling]
    E --> G[Volume Mounts]
    E --> H[Image Pulls]

Metrics to monitor:

  • Pod CPU and memory actual usage vs requested
  • Service endpoint availability
  • Persistent volume claim status and usage
  • Ingress controller backend health
  • Container image versions deployed

Alert Configuration

Critical alerts:

  • Helm release failed during production deployment
  • Pods in CrashLoopBackOff after Helm upgrade
  • Hook jobs failing repeatedly
  • Release revision rapidly increasing (indicates instability)

Warning alerts:

  • PVC pending for more than 5 minutes
  • Deployment replica count below desired
  • Image pull backoff occurring

Security Checklist

Helm charts require security review before production deployment.

Chart Security

  • Scan for vulnerabilities: Use Trivy or Snyk to scan chart dependencies

    trivy chart ./charts/myapp
  • Verify chart signatures: Use GPG signing for chart verification

    helm verify myapp-1.0.0.tgz
  • Review values files: Ensure no hardcoded secrets or insecure defaults

  • Validate values schema: Use JSON schema to enforce secure configurations

Image Security

# values.yaml security settings
image:
  repository: myregistry.myapp.com/api
  pullPolicy: IfNotPresent
  securityContext:
    runAsNonRoot: true
    runAsUser: 10000

podSecurityContext:
  runAsNonRoot: true
  fsGroup: 10000

securityContext:
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop:
      - ALL

Image hardening checklist:

  • Use specific image tags, never latest
  • Scan images for CVEs before deployment
  • Use private registry with authentication
  • Set imagePullPolicy appropriately (IfNotPresent or Always)

RBAC for Helm

Helm 3 uses the permissions of the user or service account running it.

# Create dedicated SA for Helm
kubectl create serviceaccount helm-deployer -n production

# Grant only needed permissions
kubectl create role helm-deployer --verb=get,list,watch,create,update,patch,delete \
  --resource=deployments,services,configmaps,secrets,pvc -n production

kubectl create rolebinding helm-deployer-binding \
  --role=helm-deployer --serviceaccount=production:helm-deployer -n production

Secret Management

Never:

  • Commit secrets to values files
  • Store secrets in Chart.yaml or templates
  • Use ConfigMap for sensitive data

Always:

  • Use External Secrets Operator or Vault
  • Reference secrets from external secret stores
  • Use --set-file for certificate contents

Trade-off Analysis

AspectHelmKustomizeCarvel (kapp-controller)
ModelTemplate + valuesOverlay + patchesPackage + config
Learning curveModerate (Go templates)Low (YAML only)Moderate
Reusable packagesYes (chart repos)Limited (git-based)Yes (imgpkg bundles)
DebuggingRender and inspectBuild and diffBuild and inspect
EcosystemMassive (Bitnami)GrowingSmaller but maturing
GitOps friendlyYes (with Flux/ArgoCD)NativeNative
Secret managementValues encryptionSealed secretsBank-Vaults

Helmfile vs Helm

Helmfile is a declarative deployment tool that sits above Helm, letting you manage multiple charts and environments in a single configuration file.

When Helmfile Helps

# helmfile.yaml
releases:
  - name: myapp
    chart: ./charts/myapp
    values:
      - values-prod.yaml
    set:
      - name: replicaCount
        value: 5

  - name: postgresql
    chart: bitnami/postgresql
    values:
      - db-prod.yaml

Key Differences

AspectHelm AloneHelmfile
Multi-chart manageManual per-chart commandsSingle file declares all charts
Environment configManual —values flagsEnvironments block with overrides
State trackinghelm list per namespaceDeclarative state file
TemplatingGo templates in chartsHelmfile has own templating
Sync behaviorApply individuallyAuto detects drift and syncs

CI/CD Integration

Helmfile excels in GitOps pipelines:

# Preview what would change
helmfile diff

# Apply changes
helmfile apply

# Target specific environment
helmfile --env production apply

Helmfile keeps your Helm deployments declarative and auditable. Use it when managing more than 2-3 charts or multiple environments.

OCI Registry Support

Helm 3 added native OCI support for distributing charts via container registries. This unifies chart distribution with your existing container workflow.

Login to Registry

# Login to OCI registry
helm registry login registry.example.com

# Or use keytab for automation
echo $OCI_TOKEN | helm registry login -u myuser --password-stdin registry.example.com

Push and Pull Charts

# Package chart first
helm package ./charts/myapp

# Push to OCI registry
helm push myapp-1.2.3.tgz oci://registry.example.com/charts

# Pull from registry
helm pull oci://registry.example.com/charts/myapp --version 1.2.3

Install from OCI

# Direct install from OCI
helm upgrade --install myapp oci://registry.example.com/charts/myapp \
  --version 1.2.3 \
  --namespace production

OCI Workflow Benefits

  • Unified auth: Use same credentials as container images
  • Existing infra: No separate chart repository server needed
  • Mirroring: Charts travel with container images in air-gapped setups
  • Versioning: OCI registries handle deduplication and layering

Limitations: OCI registries do not serve index.yaml, so helm search does not work directly. Use helm fetch with exact versions.

Rollback Strategies

Helm tracks every release revision. When deployments go sideways, you have multiple rollback strategies.

Basic Rollback

# Rollback to previous revision
helm rollback myapp --namespace production

# Rollback to specific revision
helm rollback myapp 3 --namespace production

Rollback with Dry-Run

Always verify before rolling back in production:

# See what the rollback would do
helm rollback myapp --dry-run --debug --namespace production

# Check release history first
helm history myapp --namespace production

Output:

REVISION  UPDATED                  STATUS     CHART           DESCRIPTION
1         2026-03-15 10:30:00     superseded myapp-1.0.0    Install complete
2         2026-03-15 11:45:00     superseded myapp-1.1.0    Upgrade complete
3         2026-03-15 14:20:00     failed     myapp-1.2.0     Upgrade failed
4         2026-03-15 14:25:00     deployed   myapp-1.1.0     Rollback complete

Selective Rollback to Known Good State

For critical applications, rollback to revision you know is stable:

# Identify stable revision
helm history myapp --namespace production | grep superseded

# Rollback two revisions (from bad upgrade to last good)
helm rollback myapp 2 --namespace production

Automated Rollback in CI/CD

# In your deployment pipeline
helm upgrade --install myapp ./charts/myapp \
  --values values-prod.yaml \
  --namespace production \
  --atomic \
  --timeout 5m

# --atomic automatically rolls back on failure

Preventing Bad Rollbacks

# Lock release so it cannot be accidentally rolled back
helm upgrade --install myapp ./charts/myapp \
  --set locked=true

# Use fail-fast hooks to catch issues early
helm upgrade myapp ./charts/myapp --timeout 2m

Interview Questions

1. What is Helm and why would you use it over plain kubectl for Kubernetes deployments?

Expected answer points:

  • Helm is the package manager for Kubernetes, bundling related manifests into a reusable chart
  • It provides templating so one chart works across dev, staging, and production with different values
  • Release management includes versioning, rollback, and upgrade tracking that kubectl lacks
  • Charts can be shared via repositories, enabling reuse of community charts like Bitnami's
2. Explain the difference between Helm 2 (Tiller) and Helm 3 architecture.

Expected answer points:

  • Helm 2 used Tiller, a server-side component that ran inside the cluster and managed releases
  • Helm 3 removed Tiller entirely, using cluster service accounts instead for security
  • Release metadata moved from cluster storage to secrets, reducing RBAC complexity
  • Three-way (or four-way) merge diffs in Helm 3 prevent accidental rollbacks on drift
3. How does the Helm values cascading hierarchy work?

Expected answer points:

  • Default values live in values.yaml inside the chart
  • Environment-specific files (values-staging.yaml, values-prod.yaml) override defaults
  • Command-line --set and --set-file flags override everything
  • Order matters: defaults < environment file < CLI flags
4. What are named templates in Helm and when would you use them?

Expected answer points:

  • Named templates (partials) live in templates/_helpers.tpl and are reusable across templates
  • Define once, include everywhere with {{ include "myapp.labels" . | nindent 4 }}
  • Common uses: chart name, fullname, labels, selectorLabels, common annotations
  • Reduce duplication and enforce consistency across Kubernetes resources
5. What are Helm hooks and how do you design them for production?

Expected answer points:

  • Hooks run Jobs or Pods at specific lifecycle points: pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete
  • Hook weight controls execution order (negative runs first)
  • Design hooks to be idempotent: use hook-delete-policy to re-run cleanly
  • Database migrations are the classic use case; they must tolerate being re-run
  • hook-delete-policy: hook-succeeded,before-hook-creation prevents duplicates
6. How do you manage secrets in Helm charts without hardcoding them?

Expected answer points:

  • Never commit secrets to values.yaml or Chart.yaml
  • Use External Secrets Operator to sync secrets from Vault, AWS Secrets Manager, or GCP Secret Manager
  • Use --set-file to load certificate or key file contents at deploy time
  • HashiCorp Vault CSI provider can inject secrets as mounted files
  • For testing, use test values that are clearly marked as non-production
7. What is the difference between Helm library charts and application charts?

Expected answer points:

  • Application charts produce actual Kubernetes resources when installed
  • Library charts set type: library and define reusable template partials (_deployment.yaml, _service.yaml)
  • Other charts depend on library charts and import their templates
  • Library charts are useful for standardizing organization-wide patterns
8. How does JSON schema validation work in Helm charts?

Expected answer points:

  • values.schema.json enforces type, constraints, and required fields on values provided to the chart
  • Helm validates during helm template and helm install/upgrade with --strict flag
  • Rejects invalid configuration before any resources are rendered
  • Example: replicaCount must be integer 1-10, image.repository is required, pullPolicy enum
9. When would you choose Helmfile over plain Helm?

Expected answer points:

  • Helmfile excels when you manage multiple charts across multiple environments in one place
  • Useful when your deployment involves 3+ charts that need to be deployed together
  • Environment blocks with overrides keep environment-specific config clean and auditable
  • helmfile diff shows exactly what would change before applying, useful in CI/CD
  • Not needed for simple single-chart deployments where Helm alone suffices
10. How do you handle Helm chart rollbacks in a CI/CD pipeline safely?

Expected answer points:

  • Always check helm history before rolling back to see all revisions
  • Use helm rollback with --dry-run --debug to preview the rollback first
  • Use the --atomic flag during upgrade to auto-rollback on failure
  • Set a timeout to prevent indefinite hangs during broken deployments
  • Store rollback playbooks in runbooks so on-call engineers do not have to improvise
11. Explain how Helm template debugging works and what tools you use.

Expected answer points:

  • Use helm template to render charts locally without cluster access
  • Use --debug with helm install/upgrade to see rendered manifests
  • Use helm get manifest to inspect what was actually deployed
  • Use --dry-run --debug to validate without making changes
  • Use printf debugging in templates with {{ .Values | toJson }}
  • Run helm lint to catch syntax errors and schema violations
12. What is the difference between Helm library charts and application charts?

Expected answer points:

  • Application charts set type: application and produce actual Kubernetes resources when installed
  • Library charts set type: library and define reusable template partials (_deployment.yaml, _service.yaml)
  • Other charts depend on library charts and import their templates via import-values
  • Library charts are useful for standardizing organization-wide patterns across teams
  • Library charts cannot install resources directly, only provide templates
13. How do chart dependencies work in Helm 3 and what is the Chart.lock file?

Expected answer points:

  • Dependencies are defined in Chart.yaml under dependencies[] with name, version, and repository
  • Run helm dependency update to download dependencies to the charts/ directory
  • Chart.lock is auto-generated and locks exact versions from the dependency resolution
  • Commit Chart.lock to ensure reproducible installs across machines
  • Use helm dependency build to install from Chart.lock without re-resolving
  • Conditions and tags in Chart.yaml control when sub-charts are enabled
14. Describe the Helm release lifecycle and how hooks interact with it.

Expected answer points:

  • Lifecycle: install → upgrade → uninstall, each with pre/post phases
  • Hook types: pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete, test
  • Hook weight determines execution order (negative weights run first)
  • Use hook-delete-policy to control when resources are cleaned up (hook-succeeded, before-hook-creation, hook-failed)
  • Hooks are Jobs that run to completion; release waits for hook to finish
  • Failed hooks mark the release as failed and block subsequent hooks
15. What are the security considerations when using Helm in a production cluster?

Expected answer points:

  • Helm 3 uses RBAC service accounts, so grant only necessary permissions
  • Never embed secrets in values files; use External Secrets Operator or Vault
  • Sign charts with GPG and verify with helm verify before installation
  • Scan charts and container images for vulnerabilities with Trivy or Snyk
  • Use --set-file for certificates instead of hardcoding values
  • Lock chart versions in Chart.lock to prevent unexpected updates
16. How does JSON schema validation prevent misconfiguration in Helm charts?

Expected answer points:

  • values.schema.json defines type constraints, required fields, and allowed values
  • Helm validates during helm template and helm install/upgrade --strict
  • Rejects invalid configuration before any resources are rendered to the cluster
  • Prevents runtime errors from missing required fields or wrong types
  • Example: replicaCount must be integer 1-10, image.repository is required, pullPolicy enum
17. When would you choose OCI registry support over traditional chart repositories?

Expected answer points:

  • Use OCI when you want unified authentication with container images (same registry credentials)
  • Useful for air-gapped environments where charts travel with container images
  • OCI registries handle deduplication and layering efficiently
  • Choose traditional repos when you need helm search repo functionality
  • OCI works well with GitOps workflows where charts are versioned alongside images
  • Limitation: no index.yaml means helm search does not work with OCI registries
18. What is Helmfile and when does it become preferable to plain Helm commands?

Expected answer points:

  • Helmfile is a declarative tool that sits above Helm, managing multiple charts in one file
  • Prefer Helmfile when managing 3+ charts or multiple environments (dev, staging, production)
  • Environment blocks with overrides keep environment-specific config auditable
  • helmfile diff shows exactly what would change before applying, useful in CI/CD
  • Single source of truth for all chart deployments in a project
  • Not needed for simple single-chart deployments where Helm alone suffices
19. How do you structure Helm charts for monorepos with multiple microservices?

Expected answer points:

  • Each microservice gets its own chart in a charts/ directory at repo root
  • Use Helmfile at the repo root to declare all chart releases and their dependencies
  • Extract shared templates into library charts that all microservices import
  • Use values-{env}.yaml files at the repo level, not inside individual charts
  • Group related services (e.g., backend-api, frontend-web) under a single release if they deploy together
  • Use hook-weight for migration jobs that must run before application pods start
20. Explain the difference between Helm's three-way (or four-way) merge in Helm 3.

Expected answer points:

  • Helm 3 introduced three-way merge to prevent accidental rollback on configuration drift
  • Three-way merge considers: last release manifest, current cluster state, new values
  • Prevents overwriting changes made directly in the cluster that are not in the previous release
  • Four-way diff (upgrade) compares: last release, current cluster, new template, new values
  • Result: only changes from new values are applied, cluster changes outside Helm are preserved
  • This makes Helm 3 safer for production use where cluster state may diverge from git

Further Reading

Conclusion

Helm makes Kubernetes deployments actually manageable. Without it, you are juggling YAML files across environments, hoping nothing drifts out of sync. The chart structure, Go templating, and dependency system feel clunky at first, but once they click, you will wonder how you lived without it.

Start by converting your existing Kubernetes manifests to a chart. Add templating for environment-specific values. Then extract reusable components into library charts as patterns emerge. You will iterate on your chart structure as you go and that is fine.

For continued learning, explore the Advanced Kubernetes guide for operators and controllers that work alongside Helm. The Prometheus & Grafana guides cover observability for your Helm-deployed applications.

Category

Related Posts

Developing Helm Charts: Templates, Values, and Testing

Create production-ready Helm charts with Go templates, custom value schemas, and testing using Helm unittest and ct.

#helm #kubernetes #devops

Helm Versioning and Rollback: Managing Application Releases

Master Helm release management—revision history, automated rollbacks, rollback strategies, and handling failed releases gracefully.

#helm #kubernetes #devops

Advanced Kubernetes: Controllers, Operators, RBAC, Production Patterns

Explore Kubernetes custom controllers, operators, RBAC, network policies, storage classes, and advanced patterns for production cluster management.

#kubernetes #containers #devops