Helm charts package Kubernetes applications as reusable, configurable units. The templating language is powerful but verbose — generating correct helpers, validating values, and structuring charts for multiple environments requires knowing patterns that Claude Code applies automatically.
Chart Structure
Create a Helm chart for our Node.js API.
Supports: multiple environments via values overrides, horizontal pod autoscaling,
configmaps from config files, secrets from external secrets operator.
my-api/
├── Chart.yaml
├── values.yaml # Defaults
├── values-staging.yaml # Staging overrides
├── values-production.yaml # Production overrides
├── templates/
│ ├── _helpers.tpl # Reusable template fragments
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ ├── configmap.yaml
│ ├── serviceaccount.yaml
│ └── externalsecret.yaml
└── tests/
└── deployment_test.yaml
# Chart.yaml
apiVersion: v2
name: my-api
description: Node.js API service
type: application
version: 0.1.0
appVersion: "1.0"
dependencies:
- name: common
version: "2.x.x"
repository: https://charts.bitnami.com/bitnami
condition: common.enabled
# values.yaml — all defaults
replicaCount: 1
image:
repository: your-registry/my-api
pullPolicy: IfNotPresent
tag: "" # Overridden at deploy time
serviceAccount:
create: true
annotations: {}
service:
type: ClusterIP
port: 80
targetPort: 3000
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 70
config:
LOG_LEVEL: info
NODE_ENV: production
externalSecrets:
enabled: false
secretStoreName: vault-backend
refreshInterval: 1h
secrets: []
# - secretKey: DATABASE_URL
# remoteRef:
# key: my-api/database
# property: url
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
Helpers Template
# templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "my-api.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this.
*/}}
{{- define "my-api.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 applied to all resources.
*/}}
{{- define "my-api.labels" -}}
helm.sh/chart: {{ include "my-api.chart" . }}
{{ include "my-api.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels — used for Service/Deployment matching.
*/}}
{{- define "my-api.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-api.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Image tag — uses .Values.image.tag if set, otherwise .Chart.AppVersion.
*/}}
{{- define "my-api.image" -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion }}
{{- printf "%s:%s" .Values.image.repository $tag }}
{{- end }}
{{/*
Validate required values.
*/}}
{{- define "my-api.validateValues" -}}
{{- if and .Values.externalSecrets.enabled (not .Values.externalSecrets.secretStoreName) }}
{{- fail "externalSecrets.secretStoreName is required when externalSecrets.enabled is true" }}
{{- end }}
{{- end }}
Deployment Template
# templates/deployment.yaml
{{- include "my-api.validateValues" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-api.fullname" . }}
labels:
{{- include "my-api.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-api.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
# Force pod restart when configmap changes
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "my-api.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "my-api.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: {{ include "my-api.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
envFrom:
- configMapRef:
name: {{ include "my-api.fullname" . }}
{{- if .Values.externalSecrets.enabled }}
- secretRef:
name: {{ include "my-api.fullname" . }}-secrets
{{- end }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
Chart Testing
# tests/deployment_test.yaml
suite: deployment tests
templates:
- deployment.yaml
- configmap.yaml
tests:
- it: should create a deployment with default values
asserts:
- isKind:
of: Deployment
- equal:
path: spec.replicas
value: 1
- matchRegex:
path: spec.template.spec.containers[0].image
pattern: "your-registry/my-api:.*"
- it: should not set replicas when autoscaling is enabled
set:
autoscaling.enabled: true
asserts:
- notExists:
path: spec.replicas
- it: should include secret envFrom when externalSecrets enabled
set:
externalSecrets.enabled: true
externalSecrets.secretStoreName: vault
template: deployment.yaml
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: RELEASE-NAME-my-api-secrets
- it: should fail when externalSecrets enabled without secretStoreName
set:
externalSecrets.enabled: true
asserts:
- failedTemplate:
errorMessage: "externalSecrets.secretStoreName is required"
# Install helm-unittest plugin and run tests
helm plugin install https://github.com/helm-unittest/helm-unittest
helm unittest my-api/
Environment-Specific Deployments
# Deploy to staging
helm upgrade --install my-api-staging ./my-api \
--namespace staging \
--create-namespace \
--values ./my-api/values-staging.yaml \
--set image.tag=1.5.2
# Deploy to production (manual approval step in your pipeline)
helm upgrade --install my-api-production ./my-api \
--namespace production \
--values ./my-api/values-production.yaml \
--set image.tag=1.5.2 \
--atomic \ # Rollback automatically on failure
--timeout 5m
For ArgoCD to manage Helm chart deployments via GitOps, see the GitOps guide. For the ingress configuration these charts reference, see the Kubernetes guide. The Claude Skills 360 bundle includes Helm skill sets for chart development, testing, and multi-environment strategies. Start with the free tier to try Helm chart scaffolding.