| name | helm-charts |
| description | Create and manage Helm charts for Kubernetes application deployment. Outputs Chart.yaml, values.yaml, templates with best practices, named templates, hooks, and chart testing configuration. |
| argument-hint | ["application name","deployment type","dependencies","environments"] |
| allowed-tools | Read, Write, Bash |
Helm Charts
Package, version, and deploy Kubernetes applications with Helm. A good chart is environment-agnostic by default and opinionated only where it matters for production safety.
Process
- Create chart scaffold with
helm create and clean out the defaults.
- Define values.yaml — all tunables at the top level, sane defaults.
- Write templates — deployment, service, ingress, configmap, HPA.
- Add named templates in
_helpers.tpl for reusable labels/selectors.
- Configure hooks for pre-install migrations and post-upgrade smoke tests.
- Validate with
helm lint and helm template --debug.
- Test with
helm test using test pods.
- Version bump in
Chart.yaml on every change.
Output Format
Chart Structure
my-app/
├── Chart.yaml # Chart metadata and dependencies
├── values.yaml # Default configuration values
├── values-staging.yaml # Environment overrides
├── values-production.yaml # Production overrides
├── .helmignore
├── templates/
│ ├── _helpers.tpl # Named templates (partials)
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml # Usually sealed or external
│ ├── hpa.yaml
│ ├── pdb.yaml # Pod Disruption Budget
│ ├── serviceaccount.yaml
│ ├── NOTES.txt # Post-install instructions
│ └── tests/
│ └── test-connection.yaml
└── charts/ # Subcharts / dependencies
Chart.yaml
apiVersion: v2
name: my-app
description: Order processing service
type: application
version: 1.4.2
appVersion: "2.1.0"
maintainers:
- name: Platform Team
email: platform@example.com
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: "17.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
values.yaml
image:
repository: ghcr.io/example/my-app
tag: ""
pullPolicy: IfNotPresent
pullSecrets: []
replicaCount: 2
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
service:
type: ClusterIP
port: 80
targetPort: 8080
annotations: {}
ingress:
enabled: false
className: nginx
{}
{}
{}
[]
{}
templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{
}}
{{ }}
{{ }}
{{ }}
{{
}}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{
}}
{{ }}
{{ }}
{{ }}
{{ }}
{{
}}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{
}}
{{ }}
{{ }}
{{ }}
templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
{{ }}
templates/hpa.yaml
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "my-app.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if }}
{{ }}
{{ }}
{{ }}
templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test"
labels:
{{- include "my-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox
command: ['wget']
args:
- '--spider'
- '--timeout=5'
- 'http://{{ include "my-app.fullname" . }}:{{ .Values.service.port }}/healthz'
Deployment Commands
helm dependency update ./my-app
helm lint ./my-app
helm lint ./my-app -f values-production.yaml
helm template my-app ./my-app -f values-production.yaml
helm install my-app ./my-app \
--namespace production \
--create-namespace \
-f values-production.yaml \
--set image.tag=2.1.0 \
--wait \
--timeout 5m
helm upgrade my-app ./my-app \
--namespace production \
-f values-production.yaml \
--set image.tag=2.1.1 \
--wait \
--timeout 5m
helm rollback my-app 1 --namespace production
helm history my-app --namespace production
helm test my-app --namespace production
helm diff upgrade my-app ./my-app -f values-production.yaml --set image.tag=2.1.1
Rules
- Checksum annotations on configmaps — force pod restarts when config changes.
- Never hardcode secrets in values files — use ExternalSecrets, Vault, or sealed secrets.
- Always set resource requests and limits — unbounded pods get evicted first.
maxUnavailable: 0 on RollingUpdate for zero-downtime deployments.
- Selector labels must be immutable — changing them requires a delete+reinstall.
readOnlyRootFilesystem: true with an emptyDir for /tmp — security hardening.
runAsNonRoot: true — never run as root in containers.
- PodDisruptionBudget — prevents all pods from being evicted simultaneously.
- Named templates in
_helpers.tpl — DRY for labels, names, selectors.
- Version bump on every chart change — enables rollback and auditing.
- Test hooks — always include a basic health check test pod.
- Use
--wait in CI — fail fast if rollout doesn't complete.