Introduction
Helm is the de facto package manager for Kubernetes, enabling developers to define, install, and upgrade complex applications through reusable chart packages. A single Helm chart can encapsulate dozens of Kubernetes resources into a versioned, configurable unit that can be deployed across multiple environments with minimal repetition.
This guide covers advanced Helm concepts including chart structure, templating, dependency management, CI/CD integration, and enterprise best practices.
Chart Structure
A well-organized Helm chart follows a standard directory layout:
my-app/
Chart.yaml # Metadata: name, version, dependencies
values.yaml # Default configuration values
values.schema.json # JSON Schema for values validation
charts/ # Sub-charts (managed by helm dependency)
templates/ # Go template YAML files
_helpers.tpl # Named template definitions
deployment.yaml
service.yaml
ingress.yaml
hpa.yaml
tests/ # Test pods for chart validation
test-connection.yaml
crds/ # Custom Resource Definitions
README.md
The `Chart.yaml` file defines metadata and dependencies:
apiVersion: v2
name: my-app
description: A production-grade web application
version: 1.2.3
appVersion: 2.0.0
kubeVersion: ">=1.25.0-0"
type: application
dependencies:
- name: postgresql
version: "12.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: "18.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
Advanced Templating
Helm uses Go templates with the Sprig function library for dynamic resource generation. Beyond simple variable substitution, you can implement complex logic:
{{- /* Conditional resource creation */}}
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-app.fullname" . }}
annotations:
{{- toYaml .Values.ingress.annotations | nindent 4 }}
spec:
ingressClassName: {{ .Values.ingress.className }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "my-app.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
Named templates in `_helpers.tpl` promote reuse:
{{- define "my-app.labels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "my-app.chart" . }}
{{- end -}}
{{- define "my-app.probe" -}}
initialDelaySeconds: {{ .initialDelay | default 5 }}
periodSeconds: {{ .period | default 10 }}
timeoutSeconds: {{ .timeout | default 3 }}
successThreshold: {{ .successThreshold | default 1 }}
failureThreshold: {{ .failureThreshold | default 3 }}
{{- end -}}
Values Management and Validation
JSON Schema validation catches configuration errors early:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 100
},
"image": {
"type": "object",
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string", "pattern": "^v?[0-9]" },
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
},
"required": ["repository"]
},
"resources": {
"type": "object",
"properties": {
"limits": { "$ref": "#/$defs/ResourceSpec" },
"requests": { "$ref": "#/$defs/ResourceSpec" }
}
}
},
"required": ["image", "replicaCount"]
}
Environment-specific overrides keep values DRY:
# values.yaml (defaults)
replicaCount: 1
image:
tag: latest
# values-staging.yaml
replicaCount: 2
image:
tag: staging-abc123
ingress:
enabled: true
hosts:
- host: staging.my-app.com
# values-production.yaml
replicaCount: 6
image:
tag: v2.0.0
resources:
limits:
cpu: 2
memory: 4Gi
requests:
cpu: 1
memory: 2Gi
ingress:
enabled: true
hosts:
- host: my-app.com
- host: www.my-app.com
Dependency Management
Lock dependency versions with `Chart.lock`:
helm dependency update ./charts/my-app
Use alias and condition fields for environment-optimized dependencies:
dependencies:
- name: postgresql
alias: primary-db
version: "12.x"
repository: https://charts.bitnami.com/bitnami
condition: primary-db.enabled
- name: postgresql
alias: replica-db
version: "12.x"
repository: https://charts.bitnami.com/bitnami
condition: replica-db.enabled
CI/CD Integration
Integrate Helm linting and testing into pipelines:
# .github/workflows/helm-release.yml
name: Helm Release
on:
push:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/setup-helm@v3
with:
version: "v3.14.0"
- run: helm lint ./charts/my-app
- run: helm template --validate ./charts/my-app
release:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/setup-helm@v3
- name: Package and push to OCI registry
run: |
helm package ./charts/my-app
helm push my-app-*.tgz oci://ghcr.io/${{ github.repository }}/charts
Chart Versioning and Publishing
Use OCI registries for chart distribution:
# Login and push
helm registry login ghcr.io -u $USER
helm push my-app-1.2.3.tgz oci://ghcr.io/my-org/charts
# Install from OCI
helm install my-app oci://ghcr.io/my-org/charts/my-app --version 1.2.3
Follow semantic versioning strictly. Breaking template changes require a major version bump, as `helm upgrade` must never silently break deployed resources.
Best Practices
Helm remains the most widely adopted packaging tool in the Kubernetes ecosystem, and mastering its advanced features is essential for operating production-grade workloads at scale.