Helm chart authoring requires understanding both Kubernetes resource structure and Go templating syntax. AI tools that know Kubernetes but not Helm idioms produce charts that deploy but are unmaintainable. This guide tests Claude and Copilot on a realistic chart for a multi-component application and evaluates the output on correctness, idiomatic Helm patterns, and schema validation.
What a Production Helm Chart Needs
values.yamlwith complete, commented defaultsvalues.schema.jsonfor input validation- Named templates in
_helpers.tplfor reusable labels and selectors - Liveness/readiness probes in deployment templates
- RBAC resources (ServiceAccount, Role, RoleBinding) with least-privilege
- Pre-install/pre-upgrade hooks for migrations
- HorizontalPodAutoscaler as optional resource
NOTES.txtwith post-install instructions
Test: Claude’s Chart for a FastAPI Application
Prompt:
Generate a complete Helm chart for a FastAPI application called 'api-service'.
Include:
- Deployment with configurable replicas, resource limits, probes
- Service (ClusterIP by default, LoadBalancer optional)
- ServiceAccount with annotation support (for IRSA/Workload Identity)
- Ingress with TLS and annotations
- HorizontalPodAutoscaler (optional, disabled by default)
- ConfigMap for app configuration
- A pre-upgrade Job hook for database migrations
- values.schema.json
App listens on port 8000. Health endpoint: /health
Claude’s _helpers.tpl:
{{/*
Expand the name of the chart.
*/}}
{{- define "api-service.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "api-service.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 }}
{{/*
Common labels
*/}}
{{- define "api-service.labels" -}}
helm.sh/chart: {{ include "api-service.chart" . }}
{{ include "api-service.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels — used in both Deployment selector and Service selector
*/}}
{{- define "api-service.selectorLabels" -}}
app.kubernetes.io/name: {{ include "api-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
Claude’s deployment template (excerpt):
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "api-service.fullname" . }}
labels:
{{- include "api-service.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "api-service.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
# Force pod restart when ConfigMap changes
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "api-service.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "api-service.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
resources:
{{- toYaml .Values.resources | nindent 12 }}
envFrom:
- configMapRef:
name: {{ include "api-service.fullname" . }}-config
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
Claude includes the checksum/config annotation — a Helm idiom that forces pod restarts when ConfigMap data changes. Copilot misses this consistently.
Pre-upgrade migration hook:
# templates/migration-job.yaml
{{- if .Values.migrations.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "api-service.fullname" . }}-migration
labels:
{{- include "api-service.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
serviceAccountName: {{ include "api-service.serviceAccountName" . }}
containers:
- name: migration
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: {{ toYaml .Values.migrations.command | nindent 12 }}
envFrom:
- configMapRef:
name: {{ include "api-service.fullname" . }}-config
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
limits:
cpu: 500m
memory: 256Mi
{{- end }}
The hook-delete-policy: before-hook-creation,hook-succeeded combination is correct: it deletes failed jobs (so you can retry) and successful ones (to not accumulate). Claude gets this right; it’s a common source of confusion.
values.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"description": "Number of pod replicas (ignored if autoscaling.enabled is true)"
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": {
"type": "string",
"description": "Container image repository"
},
"tag": {
"type": "string",
"description": "Image tag (defaults to chart appVersion)"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"],
"default": "IfNotPresent"
}
}
},
"autoscaling": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": false
},
"minReplicas": {
"type": "integer",
"minimum": 1
},
"maxReplicas": {
"type": "integer",
"minimum": 1
},
"targetCPUUtilizationPercentage": {
"type": "integer",
"minimum": 1,
"maximum": 100
}
}
}
}
}
Linting the Output
# Install helm and chart-testing
brew install helm chart-testing
# Lint the chart
helm lint ./api-service/
# Template test (dry run)
helm template test-release ./api-service/ \
--set image.repository=myapp \
--set image.tag=latest \
--debug
# Run ct lint (checks values schema, maintainers, etc.)
ct lint --charts ./api-service/
Claude’s charts pass helm lint on first generation ~85% of the time. Copilot’s charts require 2-3 rounds of fixing.
Copilot Comparison
Copilot performs well when editing inside existing chart files — it completes with blocks, adds missing toYaml pipes, and suggests correct nindent values. Where it fails:
- Doesn’t generate
_helpers.tplwith standard named templates from scratch - Misses
checksum/configannotation pattern - Hook
delete-policyoften incorrect or missing values.schema.jsonnot generated without explicit prompting
Helm Chart Scaffolding with Claude Code
# Use Claude Code for end-to-end chart generation
claude "Create a complete production Helm chart directory structure for [app].
Include all templates, values.yaml with comments, values.schema.json,
Chart.yaml, _helpers.tpl, and NOTES.txt.
Use helm create conventions. Do not skip any file."
Claude Code will create the full directory tree and populate each file, respecting Helm conventions throughout.
Related Articles
- AI Tools for Writing Kubernetes Helm Charts 2026
- Best AI Tools for Generating Kubernetes Helm Charts 2026
- Best AI Tools for Writing Kubernetes Manifests and Helm
- Best AI Tools for Writing Kubernetes Custom Resource
- Claude vs ChatGPT for Writing Kubernetes Helm Chart Values Built by theluckystrike — More at zovo.one
| Tool | Helm Chart Generation | Template Syntax | Values Management | Pricing |
|---|---|---|---|---|
| Claude | Full chart scaffolding | Handles Go templates well | Generates structured values.yaml | API-based (per token) |
| ChatGPT (GPT-4) | Complete charts with helpers | Good Go template support | Suggests sensible defaults | $20/month (Plus) |
| GitHub Copilot | Inline YAML completion | Autocompletes template blocks | Context-aware value suggestions | $10-39/user/month |
| Cursor | Project-aware chart generation | Reads existing templates | Cross-file value references | $20/month (Pro) |
| Codeium | Fast YAML suggestions | Basic template completion | Limited context | Free tier available |
Frequently Asked Questions
Are free AI tools good enough for ai tools for writing helm charts?
Free tiers work for basic tasks and evaluation, but paid plans typically offer higher rate limits, better models, and features needed for professional work. Start with free options to find what works for your workflow, then upgrade when you hit limitations.
How do I evaluate which tool fits my workflow?
Run a practical test: take a real task from your daily work and try it with 2-3 tools. Compare output quality, speed, and how naturally each tool fits your process. A week-long trial with actual work gives better signal than feature comparison charts.
Do these tools work offline?
Most AI-powered tools require an internet connection since they run models on remote servers. A few offer local model options with reduced capability. If offline access matters to you, check each tool’s documentation for local or self-hosted options.
How quickly do AI tool recommendations go out of date?
AI tools evolve rapidly, with major updates every few months. Feature comparisons from 6 months ago may already be outdated. Check the publication date on any review and verify current features directly on each tool’s website before purchasing.
Should I switch tools if something better comes out?
Switching costs are real: learning curves, workflow disruption, and data migration all take time. Only switch if the new tool solves a specific pain point you experience regularly. Marginal improvements rarely justify the transition overhead.