Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Deploy and manage Kubernetes workloads: manifests, RBAC, Helm charts, service mesh, GitOps, and troubleshooting.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/helm-charts.md
1# Helm Charts23## Chart Structure45```6mychart/7├── Chart.yaml # Chart metadata8├── values.yaml # Default values9├── values.schema.json # Values validation schema10├── charts/ # Dependency charts11├── templates/ # Template files12│ ├── NOTES.txt # Post-install notes13│ ├── _helpers.tpl # Template helpers14│ ├── deployment.yaml15│ ├── service.yaml16│ ├── ingress.yaml17│ ├── configmap.yaml18│ ├── secret.yaml19│ ├── serviceaccount.yaml20│ ├── hpa.yaml21│ └── tests/22│ └── test-connection.yaml23├── .helmignore # Ignore patterns24└── README.md # Chart documentation25```2627## Chart.yaml2829```yaml30apiVersion: v231name: myapp32description: A Helm chart for MyApp on Kubernetes33type: application34version: 1.2.035appVersion: "2.5.0"3637keywords:38- web39- application40- microservice4142home: https://example.com43sources:44- https://github.com/example/myapp4546maintainers:47- name: DevOps Team48email: [email protected]49url: https://example.com/team5051icon: https://example.com/logo.png5253dependencies:54- name: postgresql55version: "12.x.x"56repository: https://charts.bitnami.com/bitnami57condition: postgresql.enabled58tags:59- database6061- name: redis62version: "17.x.x"63repository: https://charts.bitnami.com/bitnami64condition: redis.enabled65tags:66- cache6768annotations:69category: Application70```7172## values.yaml7374```yaml75# Default values for myapp76replicaCount: 37778image:79repository: myregistry.io/myapp80pullPolicy: IfNotPresent81tag: "" # Overrides the image tag (default is .Chart.AppVersion)8283imagePullSecrets:84- name: registry-credentials8586nameOverride: ""87fullnameOverride: ""8889serviceAccount:90create: true91annotations: {}92name: ""9394podAnnotations:95prometheus.io/scrape: "true"96prometheus.io/port: "8080"9798podSecurityContext:99runAsNonRoot: true100runAsUser: 1000101fsGroup: 2000102seccompProfile:103type: RuntimeDefault104105securityContext:106allowPrivilegeEscalation: false107capabilities:108drop:109- ALL110readOnlyRootFilesystem: true111112service:113type: ClusterIP114port: 80115targetPort: 8080116annotations: {}117118ingress:119enabled: true120className: "nginx"121annotations:122cert-manager.io/cluster-issuer: "letsencrypt-prod"123nginx.ingress.kubernetes.io/ssl-redirect: "true"124hosts:125- host: myapp.example.com126paths:127- path: /128pathType: Prefix129tls:130- secretName: myapp-tls131hosts:132- myapp.example.com133134resources:135limits:136cpu: 500m137memory: 512Mi138requests:139cpu: 100m140memory: 128Mi141142autoscaling:143enabled: true144minReplicas: 3145maxReplicas: 10146targetCPUUtilizationPercentage: 80147targetMemoryUtilizationPercentage: 80148149nodeSelector: {}150151tolerations: []152153affinity:154podAntiAffinity:155preferredDuringSchedulingIgnoredDuringExecution:156- weight: 100157podAffinityTerm:158labelSelector:159matchExpressions:160- key: app.kubernetes.io/name161operator: In162values:163- myapp164topologyKey: kubernetes.io/hostname165166livenessProbe:167httpGet:168path: /health169port: http170initialDelaySeconds: 30171periodSeconds: 10172timeoutSeconds: 5173failureThreshold: 3174175readinessProbe:176httpGet:177path: /ready178port: http179initialDelaySeconds: 10180periodSeconds: 5181timeoutSeconds: 3182failureThreshold: 2183184env:185- name: ENVIRONMENT186value: production187- name: LOG_LEVEL188value: info189190envFrom: []191192volumeMounts: []193volumes: []194195# PostgreSQL dependency196postgresql:197enabled: true198auth:199username: myapp200password: "" # Set via --set or separate secret201database: myapp202primary:203persistence:204enabled: true205size: 10Gi206207# Redis dependency208redis:209enabled: true210architecture: standalone211auth:212enabled: true213password: ""214master:215persistence:216enabled: true217size: 5Gi218```219220## templates/_helpers.tpl221222```yaml223{{/*224Expand the name of the chart.225*/}}226{{- define "myapp.name" -}}227{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}228{{- end }}229230{{/*231Create a default fully qualified app name.232*/}}233{{- define "myapp.fullname" -}}234{{- if .Values.fullnameOverride }}235{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}236{{- else }}237{{- $name := default .Chart.Name .Values.nameOverride }}238{{- if contains $name .Release.Name }}239{{- .Release.Name | trunc 63 | trimSuffix "-" }}240{{- else }}241{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}242{{- end }}243{{- end }}244{{- end }}245246{{/*247Create chart name and version as used by the chart label.248*/}}249{{- define "myapp.chart" -}}250{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}251{{- end }}252253{{/*254Common labels255*/}}256{{- define "myapp.labels" -}}257helm.sh/chart: {{ include "myapp.chart" . }}258{{ include "myapp.selectorLabels" . }}259{{- if .Chart.AppVersion }}260app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}261{{- end }}262app.kubernetes.io/managed-by: {{ .Release.Service }}263{{- end }}264265{{/*266Selector labels267*/}}268{{- define "myapp.selectorLabels" -}}269app.kubernetes.io/name: {{ include "myapp.name" . }}270app.kubernetes.io/instance: {{ .Release.Name }}271{{- end }}272273{{/*274Create the name of the service account to use275*/}}276{{- define "myapp.serviceAccountName" -}}277{{- if .Values.serviceAccount.create }}278{{- default (include "myapp.fullname" .) .Values.serviceAccount.name }}279{{- else }}280{{- default "default" .Values.serviceAccount.name }}281{{- end }}282{{- end }}283```284285## templates/deployment.yaml286287```yaml288apiVersion: apps/v1289kind: Deployment290metadata:291name: {{ include "myapp.fullname" . }}292labels:293{{- include "myapp.labels" . | nindent 4 }}294spec:295{{- if not .Values.autoscaling.enabled }}296replicas: {{ .Values.replicaCount }}297{{- end }}298selector:299matchLabels:300{{- include "myapp.selectorLabels" . | nindent 6 }}301template:302metadata:303annotations:304checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}305{{- with .Values.podAnnotations }}306{{- toYaml . | nindent 8 }}307{{- end }}308labels:309{{- include "myapp.selectorLabels" . | nindent 8 }}310spec:311{{- with .Values.imagePullSecrets }}312imagePullSecrets:313{{- toYaml . | nindent 8 }}314{{- end }}315serviceAccountName: {{ include "myapp.serviceAccountName" . }}316securityContext:317{{- toYaml .Values.podSecurityContext | nindent 8 }}318containers:319- name: {{ .Chart.Name }}320securityContext:321{{- toYaml .Values.securityContext | nindent 12 }}322image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"323imagePullPolicy: {{ .Values.image.pullPolicy }}324ports:325- name: http326containerPort: {{ .Values.service.targetPort }}327protocol: TCP328{{- with .Values.env }}329env:330{{- toYaml . | nindent 12 }}331{{- end }}332{{- with .Values.envFrom }}333envFrom:334{{- toYaml . | nindent 12 }}335{{- end }}336livenessProbe:337{{- toYaml .Values.livenessProbe | nindent 12 }}338readinessProbe:339{{- toYaml .Values.readinessProbe | nindent 12 }}340resources:341{{- toYaml .Values.resources | nindent 12 }}342{{- with .Values.volumeMounts }}343volumeMounts:344{{- toYaml . | nindent 12 }}345{{- end }}346{{- with .Values.volumes }}347volumes:348{{- toYaml . | nindent 8 }}349{{- end }}350{{- with .Values.nodeSelector }}351nodeSelector:352{{- toYaml . | nindent 8 }}353{{- end }}354{{- with .Values.affinity }}355affinity:356{{- toYaml . | nindent 8 }}357{{- end }}358{{- with .Values.tolerations }}359tolerations:360{{- toYaml . | nindent 8 }}361{{- end }}362```363364## templates/hpa.yaml365366```yaml367{{- if .Values.autoscaling.enabled }}368apiVersion: autoscaling/v2369kind: HorizontalPodAutoscaler370metadata:371name: {{ include "myapp.fullname" . }}372labels:373{{- include "myapp.labels" . | nindent 4 }}374spec:375scaleTargetRef:376apiVersion: apps/v1377kind: Deployment378name: {{ include "myapp.fullname" . }}379minReplicas: {{ .Values.autoscaling.minReplicas }}380maxReplicas: {{ .Values.autoscaling.maxReplicas }}381metrics:382{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}383- type: Resource384resource:385name: cpu386target:387type: Utilization388averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}389{{- end }}390{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}391- type: Resource392resource:393name: memory394target:395type: Utilization396averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}397{{- end }}398{{- end }}399```400401## Helm Hooks402403### Pre-Install Hook (Database Migration)404405```yaml406apiVersion: batch/v1407kind: Job408metadata:409name: {{ include "myapp.fullname" . }}-migration410labels:411{{- include "myapp.labels" . | nindent 4 }}412annotations:413"helm.sh/hook": pre-install,pre-upgrade414"helm.sh/hook-weight": "0"415"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded416spec:417backoffLimit: 3418template:419metadata:420labels:421app: migration422spec:423restartPolicy: Never424containers:425- name: migrate426image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"427command: ["/app/migrate", "up"]428env:429- name: DATABASE_URL430valueFrom:431secretKeyRef:432name: {{ include "myapp.fullname" . }}-secrets433key: database-url434```435436### Post-Install Hook (Test)437438```yaml439apiVersion: v1440kind: Pod441metadata:442name: {{ include "myapp.fullname" . }}-test443labels:444{{- include "myapp.labels" . | nindent 4 }}445annotations:446"helm.sh/hook": test447"helm.sh/hook-weight": "0"448"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded449spec:450restartPolicy: Never451containers:452- name: test453image: curlimages/curl:latest454command: ['sh', '-c']455args:456- |457curl -f http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health || exit 1458```459460## Helm Commands461462```bash463# Create new chart464helm create myapp465466# Lint chart467helm lint myapp/468469# Template rendering (dry-run)470helm template myapp ./myapp -f values-prod.yaml471472# Install chart473helm install myapp ./myapp \474--namespace production \475--create-namespace \476--values values-prod.yaml \477--set image.tag=v1.2.0478479# Upgrade chart480helm upgrade myapp ./myapp \481--namespace production \482--values values-prod.yaml \483--set image.tag=v1.3.0 \484--atomic \485--timeout 5m486487# Rollback488helm rollback myapp 1 --namespace production489490# List releases491helm list --namespace production492493# Get values494helm get values myapp --namespace production495496# Get manifest497helm get manifest myapp --namespace production498499# Uninstall500helm uninstall myapp --namespace production501502# Test503helm test myapp --namespace production504505# Package chart506helm package myapp/ --version 1.2.0507508# Dependency update509helm dependency update myapp/510```511512## values-prod.yaml (Environment Override)513514```yaml515replicaCount: 5516517image:518tag: v1.2.0519520resources:521limits:522cpu: 1000m523memory: 1Gi524requests:525cpu: 250m526memory: 256Mi527528autoscaling:529enabled: true530minReplicas: 5531maxReplicas: 20532533ingress:534hosts:535- host: app.production.example.com536paths:537- path: /538pathType: Prefix539540postgresql:541enabled: true542primary:543persistence:544size: 100Gi545resources:546limits:547cpu: 2000m548memory: 4Gi549requests:550cpu: 500m551memory: 1Gi552553redis:554enabled: true555master:556persistence:557size: 20Gi558```559560## Chart Testing561562### Helm Test Command563564```bash565# Run chart tests after installation566helm test myapp --namespace production567568# Run tests with logs569helm test myapp --namespace production --logs570571# Run tests with timeout572helm test myapp --namespace production --timeout 5m573```574575### Chart Testing Tool (ct)576577```bash578# Install chart-testing579brew install chart-testing580581# Lint charts582ct lint --config ct.yaml583584# Lint and install (CI/CD)585ct lint-and-install --config ct.yaml586587# Test changed charts only588ct lint-and-install --target-branch main --config ct.yaml589```590591```yaml592# ct.yaml - Chart Testing configuration593remote: origin594target-branch: main595chart-dirs:596- charts597chart-repos:598- bitnami=https://charts.bitnami.com/bitnami599helm-extra-args: --timeout 600s600validate-maintainers: true601check-version-increment: true602```603604### Unit Testing with helm-unittest605606```bash607# Install plugin608helm plugin install https://github.com/helm-unittest/helm-unittest609610# Run tests611helm unittest ./mychart612```613614```yaml615# tests/deployment_test.yaml616suite: deployment tests617templates:618- templates/deployment.yaml619tests:620- it: should create deployment with correct replicas621set:622replicaCount: 5623asserts:624- isKind:625of: Deployment626- equal:627path: spec.replicas628value: 5629630- it: should set resource limits631set:632resources:633limits:634cpu: 500m635memory: 256Mi636asserts:637- equal:638path: spec.template.spec.containers[0].resources.limits.cpu639value: 500m640641- it: should not create HPA when autoscaling disabled642set:643autoscaling:644enabled: false645template: templates/hpa.yaml646asserts:647- hasDocuments:648count: 0649```650651## Values Schema Validation652653```json654{655"$schema": "https://json-schema.org/draft-07/schema#",656"type": "object",657"required": ["image", "service"],658"properties": {659"replicaCount": {660"type": "integer",661"minimum": 1,662"maximum": 100,663"default": 1664},665"image": {666"type": "object",667"required": ["repository"],668"properties": {669"repository": {670"type": "string",671"pattern": "^[a-z0-9.-/]+$"672},673"tag": {674"type": "string"675},676"pullPolicy": {677"type": "string",678"enum": ["Always", "IfNotPresent", "Never"]679}680}681},682"service": {683"type": "object",684"properties": {685"type": {686"type": "string",687"enum": ["ClusterIP", "NodePort", "LoadBalancer"]688},689"port": {690"type": "integer",691"minimum": 1,692"maximum": 65535693}694}695},696"resources": {697"type": "object",698"properties": {699"limits": {700"$ref": "#/definitions/resourceRequirements"701},702"requests": {703"$ref": "#/definitions/resourceRequirements"704}705}706}707},708"definitions": {709"resourceRequirements": {710"type": "object",711"properties": {712"cpu": {713"type": "string",714"pattern": "^[0-9]+m?$"715},716"memory": {717"type": "string",718"pattern": "^[0-9]+(Mi|Gi)$"719}720}721}722}723}724```725726## Chart Repository727728### Create Repository729730```bash731# Package chart732helm package mychart/ --version 1.2.0 --destination ./repo733734# Generate index735helm repo index ./repo --url https://charts.example.com736737# Update index with new chart738helm repo index ./repo --url https://charts.example.com --merge ./repo/index.yaml739```740741### GitHub Pages Repository742743```yaml744# .github/workflows/release.yaml745name: Release Charts746on:747push:748branches: [main]749paths: ['charts/**']750jobs:751release:752runs-on: ubuntu-latest753steps:754- uses: actions/checkout@v4755with:756fetch-depth: 0757- name: Configure Git758run: |759git config user.name "$GITHUB_ACTOR"760git config user.email "[email protected]"761- name: Install Helm762uses: azure/setup-helm@v3763- name: Run chart-releaser764uses: helm/[email protected]765env:766CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"767```768769### OCI Registry770771```bash772# Login to registry773helm registry login myregistry.io -u user -p token774775# Push chart to OCI registry776helm push mychart-1.2.0.tgz oci://myregistry.io/charts777778# Pull from OCI779helm pull oci://myregistry.io/charts/mychart --version 1.2.0780781# Install from OCI782helm install myapp oci://myregistry.io/charts/mychart --version 1.2.0783```784785## Helm Plugins786787```bash788# helm-diff - preview upgrades789helm plugin install https://github.com/databus23/helm-diff790helm diff upgrade myapp ./mychart -f values-prod.yaml791792# helm-secrets - manage encrypted secrets793helm plugin install https://github.com/jkroepke/helm-secrets794helm secrets encrypt secrets.yaml795helm secrets decrypt secrets.yaml.enc796helm secrets install myapp ./mychart -f secrets.yaml.enc797798# helm-git - use git repos as chart sources799helm plugin install https://github.com/aslafy-z/helm-git800helm repo add mycharts git+https://github.com/myorg/charts@charts?ref=main801802# helm-s3 - S3 as chart repository803helm plugin install https://github.com/hypnoglow/helm-s3804helm s3 init s3://my-bucket/charts805helm s3 push mychart-1.2.0.tgz my-s3-repo806```807808## Complex Upgrade/Rollback809810```bash811# Upgrade with atomic (rollback on failure)812helm upgrade myapp ./mychart \813--namespace production \814--atomic \815--timeout 10m \816--wait817818# Upgrade with cleanup on failure819helm upgrade myapp ./mychart \820--namespace production \821--cleanup-on-fail822823# Force resource update (recreate)824helm upgrade myapp ./mychart \825--namespace production \826--force827828# Dry run before upgrade829helm upgrade myapp ./mychart \830--namespace production \831--dry-run \832--debug833834# Compare current vs new835helm get manifest myapp -n production > current.yaml836helm template myapp ./mychart -f values-prod.yaml > new.yaml837diff current.yaml new.yaml838839# Rollback to specific revision840helm rollback myapp 3 --namespace production841842# Rollback with wait843helm rollback myapp 3 --namespace production --wait --timeout 5m844845# View revision history846helm history myapp --namespace production847```848849## Library Charts850851```yaml852# Chart.yaml for library chart853apiVersion: v2854name: mylib855type: library856version: 1.0.0857```858859```yaml860# templates/_deployment.tpl in library861{{- define "mylib.deployment" -}}862apiVersion: apps/v1863kind: Deployment864metadata:865name: {{ include "mylib.fullname" . }}866labels:867{{- include "mylib.labels" . | nindent 4 }}868spec:869replicas: {{ .Values.replicaCount }}870selector:871matchLabels:872{{- include "mylib.selectorLabels" . | nindent 6 }}873template:874metadata:875labels:876{{- include "mylib.selectorLabels" . | nindent 8 }}877spec:878containers:879- name: {{ .Chart.Name }}880image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"881{{- end }}882```883884```yaml885# Using library chart886# Chart.yaml887dependencies:888- name: mylib889version: "1.x.x"890repository: https://charts.example.com891892# templates/deployment.yaml893{{- include "mylib.deployment" . }}894```895896## Best Practices8978981. **Versioning**: Follow semantic versioning for charts8992. **Values**: Provide sensible defaults, allow overrides9003. **Documentation**: Document all values in README9014. **Testing**: Include tests in templates/tests/9025. **Helpers**: Use _helpers.tpl for reusable templates9036. **Labels**: Include standard Kubernetes labels9047. **Annotations**: Use annotations for metadata and tools9058. **Hooks**: Use hooks for migrations, cleanup9069. **Dependencies**: Pin dependency versions90710. **Schema**: Validate values with values.schema.json90811. **Use ct** for comprehensive chart testing in CI90912. **Use helm-diff** before production upgrades91013. **Encrypt secrets** with helm-secrets or sealed-secrets91114. **Use library charts** for shared patterns91215. **Push to OCI registries** for better artifact management913