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.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- helm-charts

Installs to ~/.agents/skills/helm-charts/ for every project on this machine.

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

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.

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

--- name: helm-charts description: Authors and maintains Helm charts - Chart.yaml versioning, values.yaml design with values.schema.json, _helpers.tpl naming and labels, checksum… license: MIT ---

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

  1. Read AGENTS.md, then the chart: Chart.yaml, values.yaml, templates/, values.schema.json, charts/, and any ci/*-values.yaml used by chart-testing.
  2. Check the Helm version in CI and locally: helm version. Helm 4 (released November 2025) renames --atomic to --rollback-on-failure and --force to --force-replace; the old flags still work with deprecation warnings. Write commands for the version the pipeline runs.
  3. Find how the chart is consumed: helm upgrade --install in CI, Argo CD, Flux, or a Kustomize helmCharts entry. Consumers constrain what you can rename.
  4. 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 update writes Chart.lock; commit it. charts/*.tgz usually 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 replicas when an HPA owns scaling, or every helm upgrade resets the replica count.
  • checksum/config restarts pods when the ConfigMap changes; without it config edits silently do nothing until the next unrelated restart.
  • Use required for 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 N for nested blocks; count indentation from the column where the key’s children start. {{- trims whitespace before; misuse glues lines together.
  • lookup returns empty during helm template and --dry-run (client); do not rely on it for correctness.
  • Never generate random values (randAlphaNum passwords) 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 fail message pointing to the new name, then remove it in the next major.

Tests

  • templates/tests/*.yaml with helm.sh/hook: test run in-cluster via helm 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/ then ct install on a kind cluster) checks version bumps and installs each ci/*-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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem