Helm Charts skill for Codex
Charts that fail on bad values, not in the cluster. Codex treats a Helm chart as a versioned API: values are documented and validated by a JSON schema, selector labels stay immutable, ConfigMap changes roll pods through checksum annotations, and HPA-managed replicas are left alone. Every change is rendered and diffed against a baseline, linted strictly, unit tested with helm-unittest, and dry-run on the server before an upgrade.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- helm-charts Installs to ~/.agents/skills/helm-charts/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install helm-charts The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project helm-charts Installs to .agents/skills/helm-charts/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/helm-charts.zipunzip -o helm-charts.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) helm-charts PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $helm-charts, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Authors and maintains Helm charts - Chart.yaml versioning, values.yaml design with values.schema.json, _helpers.tpl naming and labels, checksum annotations for config rollouts, dependencies, helm test hooks, helm-unittest and chart-testing in CI, and OCI publishing. Use when creating a chart for an app, adding values or templates, debugging template rendering errors, upgrading a chart safely, or reviewing a chart pull request. Covers Helm 3 and Helm 4.
Use it when
- An application needs a new Helm chart or a cleanup of an inherited one
- Template rendering fails or produces broken indentation
- A chart change must be released without breaking existing installs
- CI should lint, unit test and install charts automatically
Use something else when
- You only need environment-specific patches on plain YAML: use kustomize-overlays
- The chart is fine and a release is failing in the cluster: use kubernetes-debugging
- You are wiring the chart into Argo CD applications: use argocd-gitops
How to check the result
helm lint --strict passes for every values file, helm-unittest suites pass, and a rendered diff against the previous chart version shows only the intended changes.
The mistake it prevents
Putting the chart version or app version into selector labels, which makes every later helm upgrade fail on an immutable field.
A good chart renders deterministic, valid manifests for every documented values
combination, rejects bad values with a clear message before touching the cluster, and
upgrades without surprise restarts or orphaned resources. Users should be able to read
values.yaml as the chart’s documentation.
Inspect first
- Read
AGENTS.md, then the chart:Chart.yaml,values.yaml,templates/,values.schema.json,charts/, and anyci/*-values.yamlused by chart-testing. - Check the Helm version in CI and locally:
helm version. Helm 4 (released November 2025) renames--atomicto--rollback-on-failureand--forceto--force-replace; the old flags still work with deprecation warnings. Write commands for the version the pipeline runs. - Find how the chart is consumed:
helm upgrade --installin CI, Argo CD, Flux, or a KustomizehelmChartsentry. Consumers constrain what you can rename. - Render the current chart before changing it, so you can diff afterwards:
helm template rel ./chart -f values-prod.yaml > /tmp/before.yaml.
Layout
chart/
├── Chart.yaml
├── values.yaml # every key documented with a comment, safe defaults
├── values.schema.json # types, enums, required keys
├── templates/
│ ├── _helpers.tpl # names, labels, selector labels
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── configmap.yaml
│ ├── hpa.yaml # wrapped in {{- if .Values.autoscaling.enabled }}
│ ├── NOTES.txt
│ └── tests/test-connection.yaml
├── ci/ # values files chart-testing installs one by one
└── tests/ # helm-unittest suites (*_test.yaml)
Chart.yaml and versioning
apiVersion: v2
name: orders
description: Orders API
type: application
version: 1.8.0 # chart version: bump on ANY chart change (semver)
appVersion: "2.14.2" # default image tag; quoted string
kubeVersion: ">=1.29.0-0"
dependencies:
- name: postgresql
version: 16.x.x
repository: oci://registry-1.docker.io/bitnamicharts
condition: postgresql.enabled
- Major bump when values keys are renamed or removed, or selectors change.
helm dependency updatewritesChart.lock; commit it.charts/*.tgzusually not.
Templates
_helpers.tpl with stable names and the standard label set:
{{- define "orders.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- define "orders.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{- define "orders.labels" -}}
{{ include "orders.selectorLabels" . }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
Deployment excerpt with the patterns that matter:
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "orders.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "orders.labels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
Template rules:
- Selector labels never include version or chart labels; selectors are immutable and a version label there breaks every upgrade.
- Omit
replicaswhen an HPA owns scaling, or everyhelm upgraderesets the replica count. checksum/configrestarts pods when the ConfigMap changes; without it config edits silently do nothing until the next unrelated restart.- Use
requiredfor values with no safe default:{{ required "image.repository is required" .Values.image.repository }}. - Quote strings that YAML could retype: versions,
"true","0755", numeric-looking IDs. toYaml | nindent Nfor nested blocks; count indentation from the column where the key’s children start.{{-trims whitespace before; misuse glues lines together.lookupreturns empty duringhelm templateand--dry-run(client); do not rely on it for correctness.- Never generate random values (
randAlphaNumpasswords) in templates; they change on every upgrade. Accept them as values or reference an existing Secret. - CRDs in
crds/are installed once and never upgraded or deleted by Helm. Ship CRD upgrades through a separate chart or manual step and say so in NOTES.txt.
values.yaml and schema
- Nest by component (
image.repository,ingress.hosts), camelCase keys, every key commented, defaults that install on a vanilla cluster. - Add
values.schema.json; Helm validates it on install, upgrade, lint and template:
{
"$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", "minLength": 1 },
"tag": { "type": "string" },
"pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"] }
}
}
}
}
- Renaming a values key is a breaking change: support the old key for one minor version
with a
failmessage pointing to the new name, then remove it in the next major.
Tests
templates/tests/*.yamlwithhelm.sh/hook: testrun in-cluster viahelm test rel; keep them to smoke checks (curl the Service).- Unit tests with the helm-unittest plugin (
helm plugin install https://github.com/helm-unittest/helm-unittest, needs network):
# tests/deployment_test.yaml
suite: deployment
templates: [templates/deployment.yaml, templates/configmap.yaml]
tests:
- it: omits replicas when autoscaling is enabled
template: templates/deployment.yaml
set: { autoscaling.enabled: true }
asserts:
- notExists: { path: spec.replicas }
- it: uses appVersion when tag is empty
template: templates/deployment.yaml
asserts:
- matchRegex: { path: "spec.template.spec.containers[0].image", pattern: ":2\\.14\\.2$" }
- In CI, chart-testing (
ct lint --charts chart/thenct installon a kind cluster) checks version bumps and installs eachci/*-values.yaml.
Workflow
- [ ] 1. Render current output for each environment values file (baseline)
- [ ] 2. Make the change in templates/values/schema; bump Chart version
- [ ] 3. helm lint --strict with each values file
- [ ] 4. helm unittest chart/ (if plugin present)
- [ ] 5. Re-render and diff against the baseline; explain every difference
- [ ] 6. helm upgrade --install --dry-run=server against a dev namespace
- [ ] 7. Only with user confirmation: upgrade a real environment, then helm test
Verification
helm lint --strict chart/ -f chart/ci/prod-values.yaml
helm template rel chart/ -f values-prod.yaml > /tmp/after.yaml && diff -u /tmp/before.yaml /tmp/after.yaml
helm template rel chart/ | kubeconform -strict -summary -ignore-missing-schemas
helm unittest chart/
helm upgrade --install rel chart/ -n dev --dry-run=server -f values-dev.yaml
# real upgrade: ask the user first; Helm 4 spells --atomic as --rollback-on-failure
helm upgrade --install rel chart/ -n dev --wait --timeout 5m --atomic
helm test rel -n dev
helm diff upgrade (helm-diff plugin) shows the live-cluster diff if installed. When
something renders wrong, helm template --debug --show-only templates/deployment.yaml
narrows it to one file.
Output
**Chart:** orders 1.8.0 -> 1.9.0 (minor: new optional values, no renames)
**Changed:** added podDisruptionBudget.*, checksum/config annotation, schema for image.*
**Render diff (prod values):** +PDB, +annotation on Deployment template (pods will roll once)
**Checks:** lint --strict clean for 3 values files, 7 unit tests pass, server dry-run ok
**Upgrade note:** none required; the pod restart on first upgrade is expected What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Helm Charts"
short_description: "Charts that fail on bad values, not in the cluster"
default_prompt: "$helm-charts Create a Helm chart for this service with an optional HPA and ingress" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.