Helm Charts skill for Claude Code

Charts that render clean and upgrade safely. Claude designs a small, documented values surface with a JSON schema, writes templates on shared helpers with immutable selector labels, and adds checksum annotations so config changes roll pods. Before any upgrade it lints, renders every environment's values through kubeconform, diffs against the live release and upgrades with wait and automatic rollback.

  • namehelm-charts
  • size242 lines · ≈2,050 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Authors and maintains Helm charts — Chart.yaml and versioning, values.yaml design with a JSON schema, named templates in _helpers.tpl, config checksums, hooks, dependencies, OCI publishing — and runs safe upgrades and rollbacks with lint, template rendering, diff, and kubeconform. Use when creating or refactoring a Helm chart, when templates render wrong YAML, when an upgrade fails or a release is stuck in pending-upgrade, or when reviewing values for multiple environments.

Try asking

  • Turn these raw Kubernetes manifests into a Helm chart with dev and prod values
  • Our helm upgrade failed and the release is stuck in pending-upgrade
  • Review this chart's values.yaml and templates before we publish it

Use it when

  • You are creating or refactoring a Helm chart
  • Templates produce invalid or badly indented YAML
  • A Helm release is stuck or an upgrade failed midway
  • Pods do not restart when a ConfigMap changes

Skip it when

  • You only need plain manifests for one environment — use kubernetes-deploy instead
  • You are provisioning the cluster or cloud resources themselves — use terraform-iac instead

How to check the result

helm lint --strict passes, every ci values file renders cleanly through kubeconform, and helm diff shows only the intended changes before the upgrade reaches deployed status.

The common mistake it prevents

Using --reuse-values on upgrade, which silently ignores new defaults added to the chart and leaves the release with stale or missing settings.

~/.claude/skills/helm-charts/SKILL.md

--- name: helm-charts description: Authors and maintains Helm charts — Chart.yaml and versioning, values.yaml design with a JSON schema, named templates in _helpers.tpl,… license: MIT ---

A good chart renders valid manifests for every supported values file, exposes a small documented values surface validated by a schema, restarts pods when their config changes, and upgrades with a reviewed diff and an automatic rollback on failure.

Ground rules

  • Render before you install: helm template + schema validation on every values file.
  • Diff before you upgrade an existing release (helm diff upgrade, plugin).
  • Upgrading, rolling back, or uninstalling a production release requires explicit user confirmation. helm uninstall deletes the release’s resources (and PVC data if the chart owns PVCs).
  • Selector labels are immutable on Deployments: never include chart version or appVersion in them.
  • Secrets do not go in committed values files. Reference existing Secrets or use External Secrets / helm-secrets with SOPS.
  • Bump version in Chart.yaml for every chart change; appVersion tracks the app image.

Workflow

- [ ] 1. Inspect: helm version, existing release state, chart layout
- [ ] 2. Design values: minimal, documented, schema-validated
- [ ] 3. Write templates with shared helpers
- [ ] 4. Lint, render, validate for every environment values file
- [ ] 5. Diff against the live release
- [ ] 6. Upgrade with wait + rollback-on-failure (after confirmation)
- [ ] 7. Verify and record the revision

1. Inspect

helm version --short
helm list -A | grep <release>
helm history <release> -n <ns> --max 10
helm get values <release> -n <ns>            # user-supplied values actually in effect
helm get manifest <release> -n <ns> | head

A status of pending-upgrade/pending-install means a previous operation died midway; resolve it (usually helm rollback to the last deployed revision) before upgrading.

2. Chart layout and values design

mychart/
├── Chart.yaml
├── Chart.lock
├── values.yaml
├── values.schema.json
├── templates/
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   ├── hpa.yaml
│   ├── NOTES.txt
│   └── tests/test-connection.yaml
└── ci/                      # values files exercised in CI
    ├── default-values.yaml
    └── prod-values.yaml
# Chart.yaml
apiVersion: v2
name: api
description: Orders API
type: application
version: 1.8.0          # chart version, SemVer, bump on every change
appVersion: "2.14.2"    # default image tag
dependencies:
  - name: postgresql
    version: "16.x.x"
    repository: oci://registry-1.docker.io/bitnamicharts
    condition: postgresql.enabled

Values rules:

  • Group by component (image, resources, ingress, autoscaling), camelCase keys.
  • Every key has a comment and a working default; the chart installs with no overrides.
  • Prefer maps over lists for things users override per environment (lists are replaced wholesale on merge, never merged).
  • Expose escape hatches once: extraEnv, extraVolumes, podAnnotations, nodeSelector, tolerations, affinity.
  • Add values.schema.json so typos and wrong types fail at install time:
{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["image"],
  "properties": {
    "replicaCount": { "type": "integer", "minimum": 1 },
    "image": {
      "type": "object",
      "required": ["repository"],
      "properties": {
        "repository": { "type": "string" },
        "tag": { "type": "string" },
        "pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"] }
      }
    }
  }
}

3. Templates

{{/* templates/_helpers.tpl */}}
{{- define "api.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}

{{- define "api.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}

{{- define "api.labels" -}}
{{ include "api.selectorLabels" . }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
# templates/deployment.yaml (excerpt)
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "api.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
      labels:
        {{- include "api.labels" . | nindent 8 }}
    spec:
      containers:
        - name: api
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: {{ required "database.existingSecret is required" .Values.database.existingSecret }}
                  key: url

Template idioms:

NeedUse
Reuse a block with indentationinclude "name" . | nindent N (not template, which cannot be piped)
Optional block{{- with .Values.x }} ... {{- end }}
Fail fast on missing inputrequired "message" .Values.x
Access root inside range/with$.Values, $.Release.Name
Strings that look like numbers/boolsquote ("1.10", "true", ports in env)
Restart on config changechecksum/config annotation as above
Run migrations before upgradeJob with helm.sh/hook: pre-install,pre-upgrade and helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded

4. Lint, render, validate

helm dependency update ./mychart
helm lint ./mychart --strict -f mychart/ci/prod-values.yaml
for f in mychart/ci/*.yaml; do
  helm template test ./mychart -f "$f" | kubeconform -strict -summary -ignore-missing-schemas || exit 1
done
helm unittest ./mychart          # if the helm-unittest plugin is used

chart-testing (ct lint, ct install) automates this across changed charts in CI.

5–6. Diff and upgrade

helm diff upgrade api ./mychart -n shop -f values-prod.yaml      # helm-diff plugin
helm upgrade --install api ./mychart -n shop -f values-prod.yaml \
  --wait --timeout 10m --atomic           # Helm 3
# Helm 4 renamed --atomic to --rollback-on-failure; check `helm upgrade --help`.

Rollback (confirm first): helm rollback api <revision> -n shop --wait.

Publish to an OCI registry:

helm package ./mychart                      # api-1.8.0.tgz
helm push api-1.8.0.tgz oci://ghcr.io/myorg/charts

Gotchas

  • --reuse-values merges with the previous release’s values and ignores new chart defaults; prefer passing full values files every time.
  • Setting a key to null in an override deletes the default; useful, and surprising.
  • --set parses large integers as float64 and commas as list separators; use --set-string or a values file.
  • CRDs in crds/ are installed once and never upgraded or deleted by Helm. Manage CRD upgrades separately.
  • Renaming a resource in a template makes Helm delete the old one and create a new one; for PVCs and Services that can mean data loss or a new load balancer IP.
  • lookup returns empty during helm template and --dry-run (client side), so templates relying on it render differently in CI.
  • Hook resources are not part of the release manifest; failed hook Jobs linger unless a delete policy is set.
  • Whitespace: {{- trims left, -}} trims right; misplaced dashes glue lines together into invalid YAML. Render and read the output.

Output

**Chart:** api 1.7.3 -> 1.8.0 (appVersion 2.14.2)
**Rendered/validated:** ci/default-values.yaml, ci/prod-values.yaml (kubeconform strict: 0 errors)
**Diff:** Deployment (image, checksum/config), ConfigMap (2 keys), new HPA
**Upgrade:** revision 23 deployed in 3m02s with --wait, rollback-on-failure enabled
**Rollback:** helm rollback api 22 -n shop

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc