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.
- name
helm-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.
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 uninstalldeletes the release’s resources (and PVC data if the chart owns PVCs). - Selector labels are immutable on Deployments: never include chart version or
appVersionin them. - Secrets do not go in committed values files. Reference existing Secrets or use External Secrets / helm-secrets with SOPS.
- Bump
versionin Chart.yaml for every chart change;appVersiontracks 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.jsonso 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:
| Need | Use |
|---|---|
| Reuse a block with indentation | include "name" . | nindent N (not template, which cannot be piped) |
| Optional block | {{- with .Values.x }} ... {{- end }} |
| Fail fast on missing input | required "message" .Values.x |
Access root inside range/with | $.Values, $.Release.Name |
| Strings that look like numbers/bools | quote ("1.10", "true", ports in env) |
| Restart on config change | checksum/config annotation as above |
| Run migrations before upgrade | Job 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-valuesmerges with the previous release’s values and ignores new chart defaults; prefer passing full values files every time.- Setting a key to
nullin an override deletes the default; useful, and surprising. --setparses large integers as float64 and commas as list separators; use--set-stringor 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.
lookupreturns empty duringhelm templateand--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.