Helm & Kubernetes Packaging

Why Package Kubernetes Apps?

18 min Lesson 1 of 28

Why Package Kubernetes Apps?

You have mastered Kubernetes primitives — Deployments, Services, ConfigMaps, Ingresses, HorizontalPodAutoscalers. Now imagine handing a colleague the task of deploying a single microservice. They need: a Deployment, a Service, a ConfigMap, a Secret reference, an Ingress rule, a PodDisruptionBudget, and a ServiceAccount. That is seven YAML files — before you touch environment-specific values. Multiply by thirty microservices across dev, staging, and production. Welcome to YAML sprawl.

The YAML Sprawl Problem

In the early Kubernetes era, teams managed raw manifests in a k8s/ directory. This approach breaks down along four fault lines:

  • Duplication. The same Deployment block is copied for every environment, with minor value differences — image tag, replica count, resource limits. A missed update in one copy is a production incident waiting to happen.
  • No atomicity. kubectl apply -f k8s/ applies files one at a time, alphabetically. If the ConfigMap apply succeeds but the Deployment fails, you are left in an unknown intermediate state with no built-in rollback.
  • Versioning is implicit at best. What version of "the app" is running in production? Git commit hashes tie code to images, but they say nothing about which collection of Kubernetes objects represents a known-good configuration.
  • Parameterisation is ad-hoc. Teams reach for sed, envsubst, or custom shell scripts to inject values. These scripts are fragile, untested, and never documented as part of the application interface.
Production failure mode: A team uses envsubst to inject the image tag into manifests before applying. A developer forgets to export the variable, envsubst replaces the placeholder with an empty string, and kubectl apply silently deploys image: "". The pod crashes immediately, but no one understands why because the command exited zero.

Consider a minimal raw-manifest layout for a single service:

k8s/ ├── base/ │ ├── deployment.yaml │ ├── service.yaml │ ├── configmap.yaml │ └── ingress.yaml ├── overlays/ │ ├── dev/ │ │ ├── deployment-patch.yaml # image tag, replicas │ │ └── configmap-patch.yaml # dev DB URL │ ├── staging/ │ │ ├── deployment-patch.yaml │ │ └── configmap-patch.yaml │ └── prod/ │ ├── deployment-patch.yaml │ └── configmap-patch.yaml

This is already 10 files for one service. With thirty services you manage 300 files, and keeping base changes in sync across overlays is a continuous manual burden. When an engineer asks "what changed between the prod deploy last Tuesday and this Tuesday?", the answer requires diffing dozens of files across multiple directories.

Helm: Kubernetes Package Manager

Helm solves YAML sprawl the same way apt, brew, and npm solved software distribution: it introduces a package format (the chart), a repository system for sharing charts, and a release model that tracks what is deployed in the cluster.

The three core concepts you must internalize from day one:

  • Chart — a directory tree of templates and default values that describes a Kubernetes application. Think of it as the source tarball.
  • Release — a named, versioned instance of a chart installed in a cluster. You can install the same chart multiple times (once per environment, or once per tenant) and each install is an independent release with its own history.
  • Repository — an HTTP server that hosts packaged charts (.tgz archives) and an index.yaml. Artifact Hub (artifacthub.io) is the public registry. Private repos are hosted on Nexus, Artifactory, OCI registries (ECR, GCR, ACR), or plain S3 buckets.
Helm chart → release lifecycle Chart templates/ + values.yaml Chart.yaml (name, version) Values --values prod.yaml Helm Engine renders templates Release myapp-prod revision 7 atomic, versioned snapshot Kubernetes Cluster Deployment + Service + Ingress … merge
Helm merges chart templates with environment values to produce a versioned release that is applied atomically to the cluster.

What Helm Actually Does — the Install/Upgrade Loop

Under the hood, helm install and helm upgrade do the following in sequence:

  1. Load the chart and merge user-supplied values over the defaults.
  2. Render all templates in templates/ via the Go template engine, producing a set of Kubernetes manifests.
  3. Run pre-install/pre-upgrade hooks (Jobs, shell scripts — covered in lesson 7).
  4. Apply all manifests to the cluster via the Kubernetes API server.
  5. Wait for resources to reach the ready state (with --wait) and roll back if the timeout expires (with --atomic).
  6. Store the rendered manifest set as a release secret in the target namespace (kubectl get secret -l owner=helm). This is the source of truth for rollbacks.
# Add the official Bitnami repo and install PostgreSQL in one command helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update helm install mydb bitnami/postgresql \ --namespace databases \ --create-namespace \ --version 15.3.2 \ --set auth.postgresPassword=s3cr3t \ --wait \ --atomic # Inspect what was created helm list -n databases helm status mydb -n databases # Roll back to the previous revision if something goes wrong helm rollback mydb 1 -n databases
Atomic deploys matter at scale. With --atomic, Helm rolls back automatically if the upgrade does not become healthy within the timeout. Google SRE practice calls for every deploy to be reversible within one minute — Helm's rollback mechanism (backed by the stored release secrets) makes that operationally feasible without scripting a manual kubectl apply to a previous manifest snapshot.

Helm vs. the Alternatives

Helm is not the only tool in this space. You will encounter these in production codebases:

  • Kustomize — built into kubectl (kubectl apply -k). Patch-based, no templating language. Excellent for simple overlays; becomes unwieldy when logic is needed (e.g. "enable this sidecar only in prod").
  • Kapp + ytt (Carvel) — used heavily at VMware/Tanzu. ytt provides Python-like logic in YAML; kapp tracks resource ownership like Helm releases but without a templating layer.
  • Jsonnet / Tanka — Grafana Labs' toolchain. Strong typing and reuse for large monorepos, steep learning curve.
  • Raw manifests + CI scripts — the most common starting point, and the fastest path to the sprawl problem described above.

Helm wins on ecosystem breadth and operational tooling. The public chart ecosystem (thousands of charts on Artifact Hub) means you install battle-tested infrastructure (Prometheus, cert-manager, Nginx Ingress) in minutes rather than writing and maintaining manifests from scratch. For custom application charts, Helm's templating is expressive enough for 95% of production use cases.

Big-tech practice: Platform teams at companies like Spotify, Airbnb, and Shopify maintain an internal Helm chart library (often called a "golden chart" or "base chart") that encodes company-wide standards — resource limits, security contexts, PodDisruptionBudgets, sidecar injection — as chart defaults. Application teams inherit these standards by using the base chart, overriding only application-specific values. This is the pattern you will build toward in this tutorial.

Setting Up Helm

Helm 3 (the current major version, released November 2019) removed the server-side Tiller component that caused RBAC nightmares in Helm 2. Every Helm 3 install is client-only and uses your local kubeconfig credentials directly.

# Install Helm 3 on Linux/macOS (verify the checksum in production environments) curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # Or with a package manager brew install helm # macOS choco install kubernetes-helm # Windows # Verify helm version # version.BuildInfo{Version:"v3.15.x", ...} # Add the stable community charts repo (now split by vendor) helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo add cert-manager https://charts.jetstack.io helm repo update # Search for charts helm search repo nginx helm search hub postgresql --max-col-width 80

With Helm installed and a kubeconfig pointing at your cluster, you are ready to move from understanding the packaging problem to writing your first chart in lesson 3. Lesson 2 covers consuming existing community charts — the fastest way to build intuition for chart anatomy before authoring your own.