# Helm: Packaging Kubernetes Applications

## 1. Why Helm

Raw YAML repeats itself across environments and services. **Helm** is the Kubernetes package manager: a **chart** is a templated bundle of manifests with default **values**; installing a chart with specific values creates a **release** that Helm can upgrade, roll back and uninstall as a unit. Charts are also how most third-party software is distributed (ingress-nginx, cert-manager, Prometheus, External Secrets — `03-configmaps_and_secrets/`).

```bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  -n ingress-nginx --create-namespace --version 4.11.3 -f values/ingress-prod.yaml
helm list -A
helm history api -n petpal
helm rollback api 4 -n petpal
helm template api ./charts/api -f values/prod.yaml   # render locally, no cluster
helm diff upgrade api ./charts/api -f values/prod.yaml   # (helm-diff plugin)
```

## 2. Chart Structure

```
charts/api/
├── Chart.yaml            # name, version (chart), appVersion, dependencies
├── values.yaml           # defaults
├── values.schema.json    # optional JSON Schema validation of values
├── templates/
│   ├── _helpers.tpl      # named templates (labels, names)
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── NOTES.txt         # printed after install
│   └── tests/test-connection.yaml
└── charts/               # vendored dependencies
```

```yaml
# Chart.yaml
apiVersion: v2
name: api
version: 1.8.0          # chart version — bump on every chart change
appVersion: "1.4.2"     # version of the app it deploys
dependencies:
  - name: redis
    version: 20.x.x
    repository: oci://registry-1.docker.io/bitnamicharts
    condition: redis.enabled
```

## 3. Templates and Values

Templates use Go templates plus Sprig functions:

```yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "api.fullname" . }}
  labels: {{- include "api.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels: {{- include "api.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels: {{- include "api.selectorLabels" . | nindent 8 }}
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
    spec:
      containers:
        - name: api
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          resources: {{- toYaml .Values.resources | nindent 12 }}
          {{- with .Values.env }}
          env:
            {{- range $k, $v := . }}
            - name: {{ $k }}
              value: {{ $v | quote }}
            {{- end }}
          {{- end }}
```

```yaml
# values.yaml
replicaCount: 2
image: { repository: ghcr.io/petpal/api, tag: "" }
resources: { requests: { cpu: 250m, memory: 256Mi }, limits: { memory: 512Mi } }
autoscaling: { enabled: false, minReplicas: 2, maxReplicas: 10, targetCPU: 65 }
ingress: { enabled: false, host: "" }
env: { LOG_LEVEL: info }
```

Override per environment with `-f values/prod.yaml` or `--set image.tag=1.4.3` (later sources win). Built-in objects: `.Values`, `.Release` (Name, Namespace, Revision), `.Chart`, `.Capabilities`, `.Files`. The `checksum/config` annotation makes Pods roll when config changes.

## 4. Hooks, Tests and Lifecycle

- **Hooks** (`helm.sh/hook: pre-upgrade`) run Jobs at lifecycle points — e.g. database migrations before an upgrade (keep them idempotent and backward compatible).
- `helm test` runs Pods annotated as tests after install.
- `helm upgrade --atomic --wait --timeout 5m` rolls back automatically if resources don't become ready.
- Charts are published to **OCI registries** (`helm push chart.tgz oci://ghcr.io/petpal/charts`) or HTTP repos; sign/verify with provenance or cosign.

## 5. Helm vs Kustomize

**Kustomize** (built into `kubectl apply -k`) uses plain YAML **bases** plus **overlays** with patches — no templating language. Many teams use Helm for third-party software and Kustomize (or Helm) for their own apps; Argo CD and Flux support both (`08-production_practices_and_gitops/`).

## Under the Hood: What `helm upgrade` Does

Helm 3 is client-only (no Tiller). `helm upgrade --install api ./chart -f prod.yaml` (1) loads the chart and dependencies, merges values (chart defaults ← parent chart values ← `-f` files in order ← `--set`), and validates against `values.schema.json`; (2) renders all templates into manifests; (3) loads the **previous release** from a Secret in the release namespace (`sh.helm.release.v1.api.v7`, a gzipped, base64-encoded record of chart, values and rendered manifests); (4) computes a **three-way strategic merge** between the old manifest, the new manifest and the live object so that changes made by others (e.g. an HPA's replica count, if not templated) are preserved where possible; (5) runs pre-upgrade hooks, applies the changes via the API server, optionally waits for readiness, runs post-upgrade hooks; (6) stores the new revision as `...v8` with status `deployed` (the old one becomes `superseded`). `helm rollback api 7` re-applies revision 7's stored manifests as a new revision 9. Because release state lives in Secrets, anyone with read access to those Secrets can read the values — including any secrets you passed as values.

![Chart templates and values from defaults, environment files and --set are merged and rendered into manifests; Helm compares them with the previous release stored in a Secret and the live objects, runs hooks, applies changes and records a new revision that can be rolled back](diagrams/01-helm-release.svg)

## Common Pitfalls

1. **Secrets in values files** committed to Git (and stored in release Secrets).
2. **Not pinning chart versions** (`--version`) — surprise upgrades.
3. **Over-templating** — charts so generic nobody can read them.
4. **Forgetting to bump `version`** in Chart.yaml.
5. **Non-idempotent migration hooks** that fail on retry.
6. **Manual `kubectl edit`** on Helm-managed resources — overwritten on next upgrade.

## Quick Reference

```
helm repo add/update · upgrade --install -f values --version --atomic --wait · list · history · rollback · template · lint
chart: Chart.yaml (version, appVersion, deps) · values.yaml (+ schema) · templates/ (_helpers.tpl, NOTES.txt, tests)
templating: {{ .Values.x }} · include/nindent/toYaml/quote/default · if/with/range · checksum annotations
hooks: pre-install/pre-upgrade Jobs · OCI registries · release state in Secrets · Kustomize = overlays, no templates
