Helm Workflow Rules
1. Chart Structure
Standard Directory Layout
mychart/
├── Chart.yaml # Chart metadata (required)
├── Chart.lock # Dependency lock file (auto-generated)
├── values.yaml # Default configuration values
├── values.schema.json # JSON Schema for values validation
├── .helmignore # Patterns to ignore when packaging
├── templates/ # Template files
│ ├── _helpers.tpl # Named template definitions
│ ├── NOTES.txt # Post-install usage notes
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── serviceaccount.yaml
│ ├── hpa.yaml
│ └── tests/
│ └── test-connection.yaml
├── charts/ # Dependency charts (auto-populated)
└── crds/ # Custom Resource Definitions
Chart.yaml Conventions
apiVersion: v2
name: myapp
description: A Helm chart for MyApp
type: application # application or library
version: 1.2.0 # Chart version (SemVer)
appVersion: "3.4.1" # Application version
kubeVersion: ">=1.28.0" # Required K8s version constraint
maintainers:
- name: team-platform
email: platform@example.com
dependencies:
- name: postgresql
version: "~15.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: postgresql.enabled
| Field | Rule |
|---|---|
version |
SemVer — bump on every chart change |
appVersion |
Match the deployed application version |
kubeVersion |
Set minimum K8s version constraint |
type |
Use library for shared templates only |
2. Values Design
Organization Principles
# Group by component, not by K8s resource type
replicaCount: 2
image:
repository: myapp
tag: "3.4.1"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
ingress:
enabled: false
className: nginx
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# Sub-chart toggle
postgresql:
enabled: true
Naming Conventions
| Pattern | Example | Purpose |
|---|---|---|
| Boolean toggle | ingress.enabled |
Enable/disable features |
| camelCase keys | replicaCount |
Helm convention |
| Nested objects | image.repository |
Group related config |
| Resource presets | resources.requests.cpu |
Standard K8s structure |
Values Schema Validation
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["image", "service"],
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1
},
"image": {
"type": "object",
"required": ["repository"],
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" }
}
}
}
}
Environment-Specific Values
values/
├── values.yaml # Defaults
├── values-dev.yaml # Dev overrides
├── values-staging.yaml # Staging overrides
└── values-prod.yaml # Production overrides
helm upgrade myapp ./mychart \
-f values.yaml \
-f values-prod.yaml \
--namespace production
3. Template Best Practices
See references/template-patterns.md for detailed patterns including helper templates, template functions, whitespace control, NOTES.txt, and testing examples.
§3.1 Library Charts for Template Sharing
Library charts (type: library) are Helm charts that contain only templates — they produce no Kubernetes resources when rendered. Use them to share common template logic across multiple application charts.
Defining a Library Chart
# lib-chart/Chart.yaml
apiVersion: v2
name: lib-chart
description: Shared Helm templates for platform services
type: library # No resources rendered — templates only
version: 1.0.0
A library chart typically contains:
lib-chart/
├── Chart.yaml # type: library
├── templates/
│ ├── _deployment.tpl # Reusable Deployment template
│ ├── _service.tpl # Reusable Service template
│ ├── _ingress.tpl # Reusable Ingress template
│ └── _helpers.tpl # Shared labels, selectors, names
Consuming a Library Chart
Reference the library chart as a dependency in the parent chart:
# app-a/Chart.yaml
apiVersion: v2
name: app-a
type: application
version: 1.0.0
dependencies:
- name: lib-chart
version: "1.x"
repository: "file://../lib-chart"
# or: repository: "oci://ghcr.io/org/charts"
Then invoke shared templates using include:
# app-a/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "lib-chart.fullname" . }}
labels:
{{- include "lib-chart.labels" . | nindent 4 }}
spec:
{{- include "lib-chart.deploymentSpec" . | nindent 2 }}
When to Use Library Charts
- Multiple services share common template patterns (Deployment, Service, Ingress structure)
- Platform teams want to enforce standard labels, annotations, or resource structures
_helpers.tplhas grown too large — split into purpose-specific library charts- Replacing copy-paste template patterns with reusable chart dependencies
Library Chart Rules
- Library charts must declare
type: library— Helm will skip resource rendering - Library charts should not include
values.yamlwith defaults — consumers provide all values - Version library charts independently — bump when shared templates change
- Use
file://for local development, OCI registries for distribution
4. Release Management
Release Naming
| Element | Convention | Example |
|---|---|---|
| Release name | {app}-{env} or {app} |
myapp-prod, myapp |
| Namespace | Match environment | production, staging |
| Chart version | SemVer, bump on change | 1.2.0 → 1.3.0 |
Install and Upgrade Commands
# Install with atomic (auto-rollback on failure)
# Helm v4: --atomic renamed to --rollback-on-failure
helm install myapp ./mychart \
--namespace production \
--create-namespace \
--atomic \
--timeout 5m \
-f values-prod.yaml
# Upgrade with wait (wait for pods ready)
helm upgrade myapp ./mychart \
--namespace production \
--atomic \
--timeout 5m \
--cleanup-on-fail \
-f values-prod.yaml
# Install or upgrade (idempotent)
helm upgrade --install myapp ./mychart \
--namespace production \
--create-namespace \
--atomic \
--timeout 5m \
-f values-prod.yaml
Helm v4 Breaking Changes:
--atomic→--rollback-on-failure,--force→--force-replace. Server-side apply is now the default for new releases. See §11 Helm v4 Migration for details.
Rollback
# View history
helm history myapp -n production
# Rollback to previous revision
helm rollback myapp 0 -n production --wait
# Rollback to specific revision
helm rollback myapp 3 -n production --wait
5. Hook Lifecycle
Available Hooks
| Hook | Timing | Use Case |
|---|---|---|
pre-install |
Before resources created | DB migration, prerequisite check |
post-install |
After resources created | Seed data, notifications |
pre-upgrade |
Before upgrade starts | DB migration, backup |
post-upgrade |
After upgrade completes | Cache warm-up, verification |
pre-delete |
Before release deleted | Data export, cleanup |
post-delete |
After release deleted | External resource cleanup |
pre-rollback |
Before rollback | Backup current state |
post-rollback |
After rollback | Verify rollback success |
Note:
crd-installhook was removed in Helm v4. Use thecrds/directory for CRD management instead.
Hook Definition
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5" # Lower runs first
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./migrate", "up"]
Hook Delete Policies
| Policy | Behavior |
|---|---|
before-hook-creation |
Delete previous hook resource before new one |
hook-succeeded |
Delete after hook succeeds |
hook-failed |
Delete after hook fails |
6. Dependency Management
Declaring Dependencies
# Chart.yaml
dependencies:
- name: postgresql
version: "~15.5"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: postgresql.enabled
alias: db
- name: redis
version: "~19.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
tags:
- cache
import-values:
- child: master.service
parent: redis.service
Commands
# Download dependencies
helm dependency update ./mychart
# Rebuild Chart.lock
helm dependency build ./mychart
# List dependencies
helm dependency list ./mychart
Sub-Chart Value Override
# values.yaml — override sub-chart values by chart name
postgresql:
enabled: true
auth:
database: myapp
username: myapp
# Using alias
db:
enabled: true
7. Testing
See references/template-patterns.md for testing examples including built-in tests, validation pipeline, and helm-unittest.
8. Security
Chart Signing
# Package and sign
helm package ./mychart --sign --key "my-key" --keyring ~/.gnupg/pubring.gpg
# Verify signature
helm verify mychart-1.2.0.tgz --keyring ~/.gnupg/pubring.gpg
# Install with verification
helm install myapp mychart-1.2.0.tgz --verify --keyring ~/.gnupg/pubring.gpg
RBAC Templates
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
Secrets Handling
- Never store plaintext secrets in
values.yaml - Use
--setor external secret managers for sensitive values - Template secrets from references, not hardcoded values
- Consider
ExternalSecretorSealedSecretCRDs instead of Helm-managed secrets
9. Repository Management
OCI Registry (Recommended)
# Login to OCI registry
helm registry login ghcr.io -u USERNAME
# Push chart
helm push mychart-1.2.0.tgz oci://ghcr.io/org/charts
# Pull chart
helm pull oci://ghcr.io/org/charts/mychart --version 1.2.0
# Install from OCI
helm install myapp oci://ghcr.io/org/charts/mychart --version 1.2.0
Versioning Strategy
| Change Type | Version Bump | Example |
|---|---|---|
| Breaking changes | Major | 1.2.0 → 2.0.0 |
| New features, non-breaking | Minor | 1.2.0 → 1.3.0 |
| Bug fixes, doc updates | Patch | 1.2.0 → 1.2.1 |
- Always bump
versionwhen chart content changes appVersiontracks the deployed application version independently
10. Helm vs Kustomize
| Criteria | Helm | Kustomize |
|---|---|---|
| Parameterization | Values-based templating | Patch-based overlays |
| Packaging | Distributable chart archives | Directory-based |
| Dependency management | Built-in | Manual |
| Release tracking | Built-in (helm history) | External (GitOps) |
| Learning curve | Higher (Go templates) | Lower (YAML patches) |
| Best for | Reusable packages, complex logic | Simple overlays, in-house apps |
When to Use Each
- Helm: Distributing charts to others, complex conditional logic, lifecycle hooks, release management
- Kustomize: Internal applications, simple environment overlays, no templating needed
- Hybrid: Use Helm for packaging + Kustomize for environment overlays (
helm template | kustomize)
11. Helm v4 Migration
Helm v4 (current: v4.1.4) introduces breaking changes from v3. Most charts work without modification, but CLI usage and some behaviors have changed.
Breaking Changes from v3
| v3 Flag / Behavior | v4 Equivalent | Notes |
|---|---|---|
--atomic |
--rollback-on-failure |
Same behavior, renamed flag |
--force |
--force-replace |
Same behavior, renamed flag |
helm registry login https://ghcr.io |
helm registry login ghcr.io |
Domain only, no https:// prefix |
crd-install hook |
Use crds/ directory |
crd-install hook removed |
| Client-side apply (default) | Server-side apply (default) | New releases use SSA |
| In-process post-renderers | Plugin-based post-renderers only | External binary or plugin required |
New Features in v4
- Wasm-based plugins — optional WebAssembly runtime for custom functionality
- OCI digest support — install charts by digest for supply chain security:
helm install myapp oci://registry/chart --version "sha256:abc..." - Multi-document values — split complex values across multiple YAML files
- Custom template functions — extend Go templates via plugins
- kstatus watcher — improved resource readiness monitoring
- Content-based caching — faster dependency resolution
Argo CD Compatibility
Important: Argo CD (as of v3.3.x) does not fully support Helm v4. Argo CD internally uses Helm 3.x. If Helm v4 is installed locally, the
argocdCLI may produce errors. Track argoproj/argo-cd#27280 for status.
Recommendation: Use Helm v3.x for Argo CD-integrated workflows. Helm v4 can be used for local development and direct CLI operations.
Migration Checklist
- Update CI/CD scripts:
--atomic→--rollback-on-failure,--force→--force-replace - Replace
crd-installhooks withcrds/directory - Update
helm registry loginto use domain names only (nohttps://) - Verify Argo CD compatibility if using GitOps workflows
- Test post-renderer plugins if using custom post-renderers
12. Anti-Patterns
- Using
helm installwithout--atomicin CI/CD — leaves failed releases behind - Hardcoding values in templates instead of using
values.yaml - Not setting
kubeVersionconstraint — chart may deploy to incompatible clusters - Skipping
values.schema.json— no validation on user-supplied values - Using
lookupfunction without fallback — breakshelm template - Not bumping chart
versionon changes — cache serves stale charts - Storing secrets in
values.yamlcommitted to VCS - Deeply nested values without documentation — users cannot discover options
- Using
helm installinstead ofhelm upgrade --install— not idempotent - Ignoring
helm lintandhelm templatein CI — catches errors too late
Related Skills
- For Argo CD + Helm integration (App of Apps, helm diff sync, Image Updater, Helm values overrides), see gitops-argocd skill — Argo CD + Helm integration is primarily handled in gitops-argocd
- For Kubernetes manifest conventions and best practices, see k8s-workflow skill
- For secret management in Helm charts, see secrets-management skill