Back to Modern DevOps & Platform Engineering Series

Part 4: Helm & Package Management

May 14, 2026 Wasil Zafar 20 min read

Master Helm — the package manager for Kubernetes — and learn to template, version, and distribute your application deployments like a pro.

Table of Contents

  1. Introduction
  2. Getting Started
  3. Templating
  4. Deployment
  5. Repositories
  6. GitOps Integration

Introduction — The YAML Problem

If you completed Part 3 on Kubernetes Fundamentals, you know deploying an application requires writing Deployments, Services, ConfigMaps, Ingresses, and more — each as a separate YAML manifest. Now imagine maintaining 15 microservices across development, staging, and production. You're staring at hundreds of nearly-identical YAML files with only a handful of values changing between environments.

This is the YAML problem: Kubernetes manifests are verbose, repetitive, and hard to manage at scale. You need a way to templatize, version, and distribute your Kubernetes configurations.

Key Insight: Helm solves three problems simultaneously — templating (parameterised manifests), packaging (bundle related resources), and release management (upgrade/rollback with history).

What is Helm?

Helm is the package manager for Kubernetes. If apt is to Ubuntu what brew is to macOS, then Helm is the equivalent for Kubernetes clusters. It introduces three core concepts:

  • Chart — A package containing all resource definitions needed to run an application in Kubernetes. Think of it as a versioned archive of templated YAML.
  • Release — A running instance of a chart deployed into a cluster. You can have multiple releases of the same chart with different configurations.
  • Repository — A collection of charts available for installation, analogous to a package registry like npm or PyPI.
Helm Ecosystem Overview
flowchart LR
    A[Chart Repository] -->|helm install| B[Helm Client]
    B -->|Render Templates| C[Kubernetes API]
    C --> D[Release in Cluster]
    D -->|helm upgrade| C
    D -->|helm rollback| C
    E[values.yaml] -->|Override| B
    F[--set flags] -->|Override| B
                            

Helm 3 (the current major version) operates as a client-side tool — there is no server-side component like the deprecated Tiller from Helm 2. This simplifies security and RBAC significantly.

Installing Helm

Helm supports multiple installation methods across all major operating systems. Choose the one appropriate for your environment:

# macOS (Homebrew)
brew install helm

# Windows (Chocolatey)
choco install kubernetes-helm

# Windows (Scoop)
scoop install helm

# Linux (snap)
sudo snap install helm --classic

# Linux (script - recommended for CI/CD)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify installation
helm version
# version.BuildInfo{Version:"v3.15.x", ...}

Once installed, verify Helm can communicate with your cluster by running helm list — this should return an empty table if no releases are deployed.

Helm Chart Structure

A Helm chart is a directory with a specific layout. Understanding this structure is essential before creating or consuming charts:

Helm Chart Anatomy
flowchart TD
    A[my-app/] --> B[Chart.yaml]
    A --> C[values.yaml]
    A --> D[templates/]
    A --> E[charts/]
    A --> F[.helmignore]
    D --> G[deployment.yaml]
    D --> H[service.yaml]
    D --> I[ingress.yaml]
    D --> J[_helpers.tpl]
    D --> K[NOTES.txt]
    E --> L[subchart/]
    B -.->|"name, version, appVersion"| B
    C -.->|"default config values"| C
    J -.->|"reusable template snippets"| J
                            

Let's examine each critical file:

# Chart.yaml - Chart metadata
apiVersion: v2
name: my-web-app
description: A production-ready web application chart
type: application
version: 1.2.0        # Chart version (SemVer)
appVersion: "3.4.1"   # Application version
keywords:
  - web
  - nodejs
  - microservice
maintainers:
  - name: Wasil Zafar
    email: wasil.zafar@gmail.com
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
Version vs AppVersion: version tracks the chart's packaging version (bump when templates change). appVersion tracks the actual application version (bump when your app code changes). They evolve independently.
# values.yaml - Default configuration
replicaCount: 2

image:
  repository: myregistry.azurecr.io/my-web-app
  tag: "3.4.1"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 3000

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilization: 70

Template Functions & Pipelines

Helm templates use the Go template language with Sprig library extensions. This gives you access to over 100 functions for string manipulation, math, encoding, and flow control.

Core Template Syntax

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-web-app.fullname" . }}
  labels:
    {{- include "my-web-app.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "my-web-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
      labels:
        {{- include "my-web-app.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.targetPort }}
              protocol: TCP
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:
            httpGet:
              path: /ready
              port: http
            initialDelaySeconds: 5
            periodSeconds: 10

Key Template Concepts

  • Pipelines — Chain functions with |: {{ .Values.name | upper | quote }}
  • Flow Controlif/else, range, with blocks for conditional and iterative rendering
  • Named Templates — Defined in _helpers.tpl with {{- define "chart.name" -}} and invoked with include
  • Whitespace Control — Use {{- and -}} to trim leading/trailing whitespace
  • nindent — Indents output by N spaces, critical for valid YAML nesting
Hands-On Exercise
Template Debugging with helm template

Before deploying, always render your templates locally to catch errors. Run helm template my-release ./my-web-app --values values-staging.yaml to see the fully rendered YAML without contacting the cluster. Pipe through kubectl apply --dry-run=client -f - for additional validation.

debugging validation dry-run

Creating a Helm Chart

The fastest way to start is with Helm's scaffolding command, then customise the generated files:

# Scaffold a new chart
helm create my-web-app

# Explore generated structure
tree my-web-app/
# my-web-app/
# ├── .helmignore
# ├── Chart.yaml
# ├── values.yaml
# ├── charts/
# └── templates/
#     ├── NOTES.txt
#     ├── _helpers.tpl
#     ├── deployment.yaml
#     ├── hpa.yaml
#     ├── ingress.yaml
#     ├── service.yaml
#     ├── serviceaccount.yaml
#     └── tests/
#         └── test-connection.yaml

# Lint the chart for errors
helm lint ./my-web-app

# Package the chart as a .tgz archive
helm package ./my-web-app
# Successfully packaged chart and saved it to: my-web-app-1.2.0.tgz

The scaffold provides sensible defaults including a Deployment, Service, Ingress, HPA, and ServiceAccount — all templated with best-practice patterns. Strip out what you don't need, customise what you do.

Deploying with Helm

Helm's release lifecycle revolves around four primary commands: install, upgrade, rollback, and uninstall.

# Install a chart (creates a new release)
helm install my-release ./my-web-app \
  --namespace production \
  --create-namespace \
  --values values-production.yaml

# Check release status
helm status my-release -n production

# List all releases
helm list --all-namespaces

# Upgrade a release (apply changes)
helm upgrade my-release ./my-web-app \
  --namespace production \
  --values values-production.yaml \
  --set image.tag="3.5.0"

# View release history
helm history my-release -n production
# REVISION  STATUS      DESCRIPTION
# 1         superseded  Install complete
# 2         deployed    Upgrade complete

# Rollback to a previous revision
helm rollback my-release 1 -n production

# Uninstall a release (removes all resources)
helm uninstall my-release -n production
Critical: Always use helm upgrade --install (the "upsert" pattern) in CI/CD pipelines. This creates the release if it doesn't exist or upgrades it if it does — making your pipeline idempotent.

Values & Overrides

Helm's value system provides a layered configuration approach. Values are merged in a specific precedence order (highest wins):

  1. --set / --set-string flags (highest precedence)
  2. -f / --values files (in order, last file wins)
  3. Parent chart's values for subcharts
  4. Chart's default values.yaml (lowest precedence)
# values-production.yaml — environment-specific overrides
replicaCount: 5

image:
  tag: "3.5.0"

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

resources:
  requests:
    cpu: 250m
    memory: 256Mi
  limits:
    cpu: "1"
    memory: 512Mi

autoscaling:
  enabled: true
  minReplicas: 5
  maxReplicas: 20
  targetCPUUtilization: 65

This layered approach lets you maintain a single chart with environment-specific values files — values-dev.yaml, values-staging.yaml, values-production.yaml — rather than duplicating entire manifests.

Helm Repositories

Helm repositories are HTTP servers hosting an index.yaml file that references packaged charts. The community has standardised on OCI registries as the modern distribution mechanism.

# Add a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

# Update repository cache
helm repo update

# Search for charts
helm search repo nginx
helm search hub prometheus  # Search Artifact Hub

# Install from a repository
helm install my-nginx ingress-nginx/ingress-nginx \
  --namespace ingress \
  --create-namespace \
  --version 4.10.0

# Pull a chart locally for inspection
helm pull bitnami/postgresql --version 12.5.6 --untar

# OCI-based registry (modern approach)
helm push my-web-app-1.2.0.tgz oci://myregistry.azurecr.io/helm
helm install my-release oci://myregistry.azurecr.io/helm/my-web-app --version 1.2.0
Security Note: Always pin chart versions in production (--version 4.10.0). Never use floating versions like latest — an upstream chart update could break your deployments without warning.

Helm Best Practices

Production-grade Helm usage requires discipline around versioning, testing, and dependency management:

Chart Versioning

  • Follow Semantic Versioning strictly — MAJOR for breaking changes, MINOR for features, PATCH for fixes
  • Bump version in Chart.yaml on every template change
  • Keep appVersion in sync with your container image tag
  • Store charts in version control alongside application code (monorepo) or a dedicated charts repository

Dependencies

  • Declare sub-charts in Chart.yaml under dependencies
  • Run helm dependency update to download and lock versions
  • Use condition fields to make dependencies optional
  • Pin dependency versions — never use wildcards in production

Hooks & Testing

  • Hooks — Execute jobs at specific lifecycle points (pre-install, post-upgrade, pre-delete) for migrations, backups, or notifications
  • helm test — Run test pods defined in templates/tests/ to validate a release is functioning correctly
  • helm lint — Static analysis for template errors before packaging
  • helm template — Render templates locally for review and CI validation
Production Pattern
CI/CD Pipeline Integration

A robust Helm CI/CD pipeline: (1) helm lint on every PR, (2) helm template + kubectl apply --dry-run=server for validation, (3) helm package + push to OCI registry on merge to main, (4) helm upgrade --install --atomic --timeout 5m for deployment with automatic rollback on failure.

CI/CD automation atomic deploys

Preparing for ArgoCD

Helm charts are first-class citizens in GitOps workflows. ArgoCD (which we'll cover in Part 6) natively supports Helm as a configuration source. Here's how Helm fits into the GitOps paradigm:

  • Chart Source — ArgoCD can pull charts from Git repos or Helm repositories directly
  • Values Management — ArgoCD's Application spec supports inline values, values files from Git, or Helm parameter overrides
  • Declarative Releases — Instead of imperative helm install, ArgoCD reconciles desired state continuously
  • Drift Detection — ArgoCD detects when live state diverges from the chart's rendered output and can auto-sync
Looking Ahead: In a GitOps workflow, you never run helm install manually. Instead, you commit chart changes and values files to Git — the GitOps controller (ArgoCD/Flux) handles deployment automatically. This eliminates "it worked on my machine" deployment issues.

The mental shift: Helm becomes your packaging and templating tool, while ArgoCD becomes your deployment operator. You still write and maintain Helm charts, but the deployment lifecycle is managed declaratively through Git commits.

Conclusion & What's Next

Helm transforms Kubernetes manifest management from a tedious, error-prone copy-paste exercise into a structured, versioned, and distributable workflow. You've learned to:

  • Understand Helm's core concepts — charts, releases, and repositories
  • Create and structure charts with proper templating using Go templates
  • Deploy, upgrade, and rollback releases with confidence
  • Manage environment-specific configurations through layered values
  • Follow production best practices for versioning, testing, and security

Next in the Series

In Part 5: GitOps Foundations & Architecture, we'll explore the GitOps methodology — declarative infrastructure, Git as the single source of truth, reconciliation loops, and how tools like ArgoCD and Flux implement these principles at scale.