Helm chart maintenance

Organize Chart.yaml, values.schema.json, overridable templates, and hook lifecycles; keep appVersion and Chart semver aligned; gate PRs with lint, template, and (optional) kubeconform.

The SKILL requires sensible values.yaml defaults and a README listing key values; avoid hard-coded image tags—inject via values and a global section.

Declare compatible ranges for subcharts/library charts; call out CRD and hook ordering risks on upgrades; route secrets through External Secrets / SealedSecrets references—never commit plaintext in the chart.

Chart release pipeline (local → CI → cluster)

  [ Chart dir & version bump ]
        │
        ▼
  ┌─────────────┐     Chart.yaml / values / schema aligned; README updated
  │ Local checks │──── helm lint, helm template (+ optional kubeconform)
  └─────────────┘
        │
        ▼
  ┌─────────────┐     ct lint, kind install matrix, artifact push
  │  CI / CD     │──── semver; major + migration notes on breaking changes
  └─────────────┘
        │
        ▼
  ┌─────────────┐     install / upgrade; history & rollback notes match release skill
  │ Cluster rel  │──── hook & CRD ordering documented in runbooks
  └─────────────┘

Before merge: rendered templates should diff cleanly; prod overlays (e.g. values-prod.yaml) are deltas only—no secrets in git.

Helm Chart directory structure explained:

mychart/
├── Chart.yaml           # Chart metadata (name, version, appVersion)
├── values.yaml          # Default values (usable out of the box, document all exported keys)
├── values.schema.json   # Type constraints and required field validation
├── README.md            # Key values explained with usage examples
├── charts/              # Subchart dependencies (populated by helm dependency update)
├── templates/
│   ├── _helpers.tpl     # Named templates (fullname, standard labels)
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── hpa.yaml
│   ├── pdb.yaml
│   ├── serviceaccount.yaml
│   ├── NOTES.txt        # Usage hints printed after install/upgrade
│   └── tests/
│       └── test-connection.yaml   # helm test Pod
├── ci/                  # CI values files used by chart-testing
│   └── ci-values.yaml
└── .helmignore          # Exclude files from Chart package

Values & schema

Use values.schema.json for types and required keys; defaults should suit a minimal demo. Share cross-subchart config under global and document override precedence (-f order, --set).

  • Images: split repository + tag; tags usually come from env-specific values, not literals in templates.
  • Resources/replicas: separate prod/stage values files; document switches that must stay off in prod (e.g. debug).
  • Docs: one line per exported value; on breaking renames keep an alias or migration note for one release.

values.yaml design principles (required/optional/env-specific) with example:

# values.yaml — defaults usable out of the box (minimal demo environment)
# Values that must be overridden externally default to "" and are marked required in schema

image:
  repository: myregistry/myapp   # required: injected by CI
  tag: ""                        # required: overridden by env values (never hardcode latest)
  pullPolicy: IfNotPresent

replicaCount: 1                  # dev default 1; prod values override to 3+

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false                 # disabled by default; prod values enable
  className: nginx
  host: ""                       # must be provided by env values
  tls: false

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: false                 # recommended to enable in production
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

# debug: false                   # must NOT be enabled in production! see values-prod.yaml comment

---
# values-prod.yaml — deltas only
# image.tag is injected by CI pipeline with --set image.tag=1.2.3
replicaCount: 3
ingress:
  enabled: true
  host: myapp.example.com
  tls: true
autoscaling:
  enabled: true
resources:
  requests:
    cpu: 250m
    memory: 256Mi
  limits:
    cpu: "1"
    memory: 1Gi

Templates & parameterization

Use {{ .Values }}, required, default, and tpl for DRY templates; wire labels/selectors to the same variables to avoid drift. Factor helpers in _helpers.tpl for full names and standard labels.

Prefer

  • include for named templates; comment in-chart conventions
  • Configurable probes, resources, securityContext
  • Render only needed resources; document conditionals/API version checks

Avoid

  • Hard-coded environment names or full image URIs without tag variables
  • Ignoring nindent/YAML spacing that creates invalid manifests
  • Depending on undocumented Helm internals

_helpers.tpl helper functions (fullname, standard labels, selectorLabels):

{{/*
  _helpers.tpl — named templates for mychart

  mychart.fullname: Chart name + release name, truncated to 63 chars
*/}}
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
  mychart.labels: standard label set for all resources
*/}}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
  mychart.selectorLabels: Deployment selector (no version to avoid update conflicts)
*/}}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/* Usage in deployment.yaml */}}
{{/*
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      {{- include "mychart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "mychart.selectorLabels" . | nindent 8 }}
*/}}

Dependencies, hooks & secrets

Chart.yaml dependencies should use compatible ranges; note subchart upgrades in CHANGELOG. Hooks need hook-weight and delete policies; if CRD ordering conflicts, dedicate a section in the SKILL or runbook.

  • Testing: chart-testing (ct) plus kind install smoke matrix.
  • Rollback: align helm rollback and release history notes with your release skill.
  • Multi-env: rules for values-prod.yaml overlays and forbidden items (e.g. dev endpoints).

CI checks & environments

PRs run helm lint and helm template; with kubeconform, pin Kubernetes versions and CRD sources. Store rendered manifests as artifacts for reviewable diffs.

Stay consistent with raw manifest skills: rendered output should meet the same label, probe, and security baseline.

Helm command snippet lab

Fill chart path and release name, select value files and flags, and copy ready-to-run helm lint / helm template snippets (tune flags for your Helm version).

Extra -f (example filenames)
Other flags

helm template prints to stdout; pipe through | tee /tmp/rendered.yaml before kubeconform. Maintain install/upgrade commands separately with cluster creds and team policies like --atomic.

---
name: helm-chart-maintenance
description: Maintain Helm charts that lint and template cleanly
tags: [helm, kubernetes, devops, chart]
---
# Directory structure
1. templates/_helpers.tpl defines fullname, labels, selectorLabels named templates
2. values.schema.json constrains types and required fields (auto-validated at helm install)
3. ci/ directory holds minimal CI values files for chart-testing

# Values design
4. Defaults usable out of the box: minimal demo doesn't need extra -f files
5. image.tag injected by env values (--set image.tag=) never hardcoded
6. Env delta files: values-dev.yaml / values-staging.yaml / values-prod.yaml contain deltas only
7. Secrets must NOT appear in values-prod.yaml; use External Secrets / SealedSecrets

# Template best practices
8. All resource labels reference include "mychart.labels" to stay DRY
9. selector and template.labels reference the same selectorLabels, no hand-written strings
10. Conditional rendering: {{ if .Values.ingress.enabled }} generates Ingress only when needed
11. required "message" .Values.image.repository errors immediately on missing required values

# Testing & CI
12. helm lint + helm template | kubeconform required on every PR
13. helm unittest (helm plugin install https://github.com/helm-unittest/helm-unittest) unit-tests template rendering
14. chart-testing (ct) + kind cluster for install/upgrade smoke tests

# Upgrades & rollback
15. Chart.yaml version follows semver; breaking changes bump major with migration notes
16. helm upgrade --atomic rolls back automatically on failure; use --timeout to prevent hangs
17. Document hook weight and delete policies; CRD install ordering gets its own runbook section

All skills More skills