Helm Chart Scaffolding
Comprehensive guidance for creating, organizing, and managing Helm charts for packaging and deploying Kubernetes applications.
Purpose
This skill provides step-by-step instructions for building production-ready Helm charts, including chart structure, templating patterns, values management, and validation strategies.
When to Use This Skill
Use this skill when you need to:
-
Create new Helm charts from scratch
-
Package Kubernetes applications for distribution
-
Manage multi-environment deployments with Helm
-
Implement templating for reusable Kubernetes manifests
-
Set up Helm chart repositories
-
Follow Helm best practices and conventions
Helm Overview
Helm is the package manager for Kubernetes that:
-
Templates Kubernetes manifests for reusability
-
Manages application releases and rollbacks
-
Handles dependencies between charts
-
Provides version control for deployments
-
Simplifies configuration management across environments
Step-by-Step Workflow
- Initialize Chart Structure
Create new chart:
helm create my-app
Standard chart structure:
my-app/ ├── Chart.yaml # Chart metadata ├── values.yaml # Default configuration values ├── charts/ # Chart dependencies ├── templates/ # Kubernetes manifest templates │ ├── NOTES.txt # Post-install notes │ ├── _helpers.tpl # Template helpers │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── serviceaccount.yaml │ ├── hpa.yaml │ └── tests/ │ └── test-connection.yaml └── .helmignore # Files to ignore
- Configure Chart.yaml
Chart metadata defines the package:
apiVersion: v2 name: my-app description: A Helm chart for My Application type: application version: 1.0.0 # Chart version appVersion: "2.1.0" # Application version
Keywords for chart discovery
keywords:
- web
- api
- backend
Maintainer information
maintainers:
- name: DevOps Team email: devops@example.com url: https://github.com/example/my-app
Source code repository
sources:
Homepage
home: https://example.com
Chart icon
icon: https://example.com/icon.png
Dependencies
dependencies:
- name: postgresql version: "12.0.0" repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled
- name: redis version: "17.0.0" repository: "https://charts.bitnami.com/bitnami" condition: redis.enabled
Reference: See assets/Chart.yaml.template for complete example
- Design values.yaml Structure
Organize values hierarchically:
Image configuration
image: repository: myapp tag: "1.0.0" pullPolicy: IfNotPresent
Number of replicas
replicaCount: 3
Service configuration
service: type: ClusterIP port: 80 targetPort: 8080
Ingress configuration
ingress: enabled: false className: nginx hosts: - host: app.example.com paths: - path: / pathType: Prefix
Resources
resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m"
Autoscaling
autoscaling: enabled: false minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 80
Environment variables
env:
- name: LOG_LEVEL value: "info"
ConfigMap data
configMap: data: APP_MODE: production
Dependencies
postgresql: enabled: true auth: database: myapp username: myapp
redis: enabled: false
Reference: See assets/values.yaml.template for complete structure
- Create Template Files
Use Go templating with Helm functions:
templates/deployment.yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-app.fullname" . }} labels: {{- include "my-app.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "my-app.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "my-app.selectorLabels" . | nindent 8 }} spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: {{ .Values.service.targetPort }} resources: {{- toYaml .Values.resources | nindent 12 }} env: {{- toYaml .Values.env | nindent 12 }}
- Create Template Helpers
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 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 "my-app.labels" -}} helm.sh/chart: {{ include "my-app.chart" . }} {{ include "my-app.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }}
{{/* Selector labels */}} {{- define "my-app.selectorLabels" -}} app.kubernetes.io/name: {{ include "my-app.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }}
- Manage Dependencies
Add dependencies in Chart.yaml:
dependencies:
- name: postgresql version: "12.0.0" repository: "https://charts.bitnami.com/bitnami" condition: postgresql.enabled
Update dependencies:
helm dependency update helm dependency build
Override dependency values:
values.yaml
postgresql: enabled: true auth: database: myapp username: myapp password: changeme primary: persistence: enabled: true size: 10Gi
- Test and Validate
Validation commands:
Lint the chart
helm lint my-app/
Dry-run installation
helm install my-app ./my-app --dry-run --debug
Template rendering
helm template my-app ./my-app
Template with values
helm template my-app ./my-app -f values-prod.yaml
Show computed values
helm show values ./my-app
Validation script:
#!/bin/bash set -e
echo "Linting chart..." helm lint .
echo "Testing template rendering..." helm template test-release . --dry-run
echo "Checking for required values..." helm template test-release . --validate
echo "All validations passed!"
Reference: See scripts/validate-chart.sh
- Package and Distribute
Package the chart:
helm package my-app/
Creates: my-app-1.0.0.tgz
Create chart repository:
Create index
helm repo index .
Upload to repository
AWS S3 example
aws s3 sync . s3://my-helm-charts/ --exclude "" --include ".tgz" --include "index.yaml"
Use the chart:
helm repo add my-repo https://charts.example.com helm repo update helm install my-app my-repo/my-app
- Multi-Environment Configuration
Environment-specific values files:
my-app/ ├── values.yaml # Defaults ├── values-dev.yaml # Development ├── values-staging.yaml # Staging └── values-prod.yaml # Production
values-prod.yaml:
replicaCount: 5
image: tag: "2.1.0"
resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m"
autoscaling: enabled: true minReplicas: 3 maxReplicas: 20
ingress: enabled: true hosts: - host: app.example.com paths: - path: / pathType: Prefix
postgresql: enabled: true primary: persistence: size: 100Gi
Install with environment:
helm install my-app ./my-app -f values-prod.yaml --namespace production
- Implement Hooks and Tests
Pre-install hook:
templates/pre-install-job.yaml
apiVersion: batch/v1 kind: Job metadata: name: {{ include "my-app.fullname" . }}-db-setup annotations: "helm.sh/hook": pre-install "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": hook-succeeded spec: template: spec: containers: - name: db-setup image: postgres:15 command: ["psql", "-c", "CREATE DATABASE myapp"] restartPolicy: Never
Test connection:
templates/tests/test-connection.yaml
apiVersion: v1 kind: Pod metadata: name: "{{ include "my-app.fullname" . }}-test-connection" annotations: "helm.sh/hook": test spec: containers:
- name: wget image: busybox command: ['wget'] args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never
Run tests:
helm test my-app
Common Patterns
Pattern 1: Conditional Resources
{{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ include "my-app.fullname" . }} spec:
...
{{- end }}
Pattern 2: Iterating Over Lists
env: {{- range .Values.env }}
- name: {{ .name }} value: {{ .value | quote }} {{- end }}
Pattern 3: Including Files
data: config.yaml: | {{- .Files.Get "config/application.yaml" | nindent 4 }}
Pattern 4: Global Values
global: imageRegistry: docker.io imagePullSecrets: - name: regcred
Use in templates:
image: {{ .Values.global.imageRegistry }}/{{ .Values.image.repository }}
Best Practices
-
Use semantic versioning for chart and app versions
-
Document all values in values.yaml with comments
-
Use template helpers for repeated logic
-
Validate charts before packaging
-
Pin dependency versions explicitly
-
Use conditions for optional resources
-
Follow naming conventions (lowercase, hyphens)
-
Include NOTES.txt with usage instructions
-
Add labels consistently using helpers
-
Test installations in all environments
Troubleshooting
Template rendering errors:
helm template my-app ./my-app --debug
Dependency issues:
helm dependency update helm dependency list
Installation failures:
helm install my-app ./my-app --dry-run --debug kubectl get events --sort-by='.lastTimestamp'
Related Skills
-
k8s-manifest-generator
-
For creating base Kubernetes manifests
-
gitops-workflow
-
For automated Helm chart deployments