- name
- helm-patterns
- description
- Use this skill when the user says 'helm', 'helm chart', 'helm template', 'helm install', 'helm upgrade', 'helm rollback', 'helmfile', 'helm secrets', 'helm values', 'helm dependency', 'helm repository', 'helm plugin', 'helm unittest', 'helm lint', 'helm package', 'helm registry', 'oci helm', 'helm hooks', 'helm tests', 'helm subchart', 'helm global values', 'helm library chart', 'helm umbrella chart', 'helm postrender', 'helm diff', 'helmfile', 'helm values schema', 'helm crd', 'chart museum', 'chart repo', 'helmfile environment'. Covers: Helm chart development, templating, dependency management, testing, CI/CD integration, security, advanced patterns, and operational best practices.
- version
- 1.0.0
- author
- j4flmao
- license
- MIT
- compatibility
- {"claude-code":true,"cursor":true,"codex":true,"windsurf":true}
- tags
- ["devops","helm","kubernetes","charts","phase-5"]
# Helm Patterns
## Purpose
Develop, test, deploy, and operate Helm charts for Kubernetes with best practices for templating, dependency management, security, CI/CD integration, and advanced patterns.
## Agent Protocol
### Trigger
Exact user phrases: "helm", "helm chart", "helm template", "helm install", "helm upgrade", "helm rollback", "helmfile", "helm secrets", "helm values", "helm dependency", "helm unittest", "helm lint", "helm CRD", "chart museum".
### Input Context
- Chart purpose (app, infrastructure, library).
- Kubernetes version and distribution.
- CI/CD system (GitHub Actions, GitLab CI, ArgoCD).
- Secrets management (SOPS, SealedSecrets, External Secrets, Vault).
- Multi-environment strategy.
### Output Artifact
Helm chart files (Chart.yaml, templates/, values.yaml), helmfile configuration, or CI/CD config.
### Response Format
Chart files, helmfile YAML, or CI/CD pipeline YAML. No preamble.
### Completion Criteria
- [ ] Chart.yaml with correct metadata, version, appVersion, dependencies.
- [ ] values.yaml with all configurable parameters documented.
- [ ] Templates with proper indentation, helpers, and conditionals.
- [ ] Hooks for pre/post install/upgrade/delete operations.
- [ ] Unittests covering key template outputs.
- [ ] CI/CD pipeline with lint, unittest, package, push.
- [ ] Secrets management integrated (SOPS or SealedSecrets).
- [ ] Multi-environment values files (values-dev.yaml, values-prod.yaml).
### Max Response Length
400 lines.
## Quick Start
`helm create my-chart` → Edit `values.yaml` → Write templates in `templates/` → Add dependencies in `Chart.yaml` → `helm lint` → `helm template --debug` → `helm unittest` → Package and push to registry → Deploy with `helm upgrade --install`.
## Decision Tree: Chart Types
| Chart Type | Purpose | Dependencies | Versioning |
|------------|---------|--------------|------------|
| **Application Chart** | Deploys an application | None or subcharts | Semantic, bump per release |
| **Library Chart** | Provides helpers/snippets | No templates, only _helpers.tpl | Semantic, shared across teams |
| **Umbrella Chart** | Composes multiple subcharts | Many subchart deps | Version matches release bundle |
| **Infrastructure Chart** | Deploys operators, CRDs | CRD charts or depends-on | Tied to operator version |
| **OCI Chart** | Stored in OCI registry | Same as regular | OCI tag equals chart version |
## Core Workflow
### Step 1: Chart Structure
```
my-chart/
├── Chart.yaml # Metadata + dependencies
├── values.yaml # Default values
├── values.schema.json # JSON Schema validation
├── charts/ # Subcharts (packed dependencies)
├── templates/
│ ├── _helpers.tpl # Template helpers (Go templates)
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── hpa.yaml
│ ├── pdb.yaml
│ ├── servicemonitor.yaml
│ ├── tests/
│ │ └── test-connection.yaml
│ └── NOTES.txt # Post-install output
├── ci/ # CI test values
│ ├── default-values.yaml
│ └── with-ingress.yaml
└── tests/ # Unit tests (helm-unittest)
├── deployment_test.yaml
└── service_test.yaml
```
### Step 2: Chart.yaml with Dependencies
```yaml
apiVersion: v2
name: my-app
description: A production-grade web application
type: application
version: 1.2.3
appVersion: "2.5.0"
kubeVersion: ">=1.27.0-0"
home: https://github.com/myorg/my-app
sources:
- https://github.com/myorg/my-app
maintainers:
- name: DevOps Team
email: devops@example.com
dependencies:
- name: postgresql
version: "15.5.2"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
tags:
- database
- name: redis
version: "19.6.1"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: redis.enabled
- name: common
version: "2.x"
repository: "file://../common"
- name: monitoring
version: "0.x"
repository: "https://prometheus-community.github.io/helm-charts"
alias: prometheus
import-values:
- child: defaults
parent: monitoring
```
### Step 3: values.yaml (with documentation)
```yaml
# @section Global parameters
# @param global.imageRegistry Global Docker image registry
# @param global.imagePullSecrets Global Docker image pull secrets
global:
imageRegistry: ""
imagePullSecrets: []
# @section Application parameters
# @param image.repository Image repository
# @param image.tag Image tag (overrides appVersion)
# @param image.pullPolicy Image pull policy
# @param replicaCount Number of replicas
image:
repository: myorg/my-app
tag: ""
pullPolicy: IfNotPresent
replicaCount: 3
# @section Service parameters
service:
type: ClusterIP
port: 8080
containerPort: 8080
# @section Ingress parameters
ingress:
enabled: false
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.example.com
# @section Resource requests and limits
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
# @section Autoscaling
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
# @section PodDisruptionBudget
pdb:
enabled: true
minAvailable: 2
# @section Security
podSecurityContext:
fsGroup: 1001
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
containerSecurityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
```
### Step 4: Core Templates
```yaml
# templates/_helpers.tpl
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "my-app.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 }}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.name" . }}-{{ .Chart.Version | replace "+" "_" }}
{{ include "my-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "my-app.image" -}}
{{- $registry := .Values.global.imageRegistry | default .Values.image.repository -}}
{{- $tag := .Values.image.tag | default (printf "v%s" .Chart.AppVersion) -}}
{{- printf "%s:%s" $registry $tag -}}
{{- end }}
```
```yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
{{- if .Values.autoscaling.enabled }}
replicas: {{ .Values.autoscaling.minReplicas }}
{{- end }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "my-app.serviceAccountName" . }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ include "my-app.image" . }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.containerSecurityContext }}
securityContext:
{{- toYaml . | nindent 10 }}
{{- end }}
ports:
- containerPort: {{ .Values.service.containerPort }}
name: http
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 5
periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 10 }}
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
{{- if .Values.configmap.enabled }}
envFrom:
- configMapRef:
name: {{ include "my-app.fullname" . }}
{{- end }}
volumeMounts:
- name: tmp
mountPath: /tmp
{{- if .Values.extraVolumeMounts }}
{{- toYaml .Values.extraVolumeMounts | nindent 8 }}
{{- end }}
volumes:
- name: tmp
emptyDir: {}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
```
### Step 5: Helm Hooks
```yaml
# templates/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ include "my-app.image" . }}"
command: ["python", "manage.py", "migrate"]
```
```yaml
# templates/test-connection.yaml (helm test)
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: curlimages/curl:latest
command: ['sh', '-c', 'curl -sf http://{{ include "my-app.fullname" . }}:{{ .Values.service.port }}/healthz']
restartPolicy: Never
```
### Step 6: Helm Unit Tests
```yaml
# tests/deployment_test.yaml
suite: test deployment
templates:
- deployment.yaml
tests:
- it: should have correct number of replicas
set:
replicaCount: 5
asserts:
- isKind:
of: Deployment
- equal:
path: spec.replicas
value: 5
- it: should set environment variables
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- it: should have security context
asserts:
- isNotNull:
path: spec.template.spec.securityContext
- equal:
path: spec.template.spec.securityContext.runAsNonRoot
value: true
- it: should have resource limits
set:
GitHubで見る