Helm Package Manager
Overview
Helm is the package manager for Kubernetes, enabling you to define, install, and upgrade complex Kubernetes applications through reusable charts. A Helm chart packages Kubernetes manifests with templating, making applications configurable and shareable. Instead of maintaining dozens of YAML files with copy-paste duplication across environments, Helm charts parameterize configurations, enabling the same chart to deploy to dev, staging, and production with different values.
Helm solves configuration management complexity. A typical microservice requires Deployment, Service, Ingress, ConfigMap, Secret, HorizontalPodAutoscaler, PodDisruptionBudget, and ServiceMonitor resources. Without Helm, you maintain separate YAML files for each environment, leading to drift and copy-paste errors. With Helm, you define templates once and override values per environment (values-dev.yaml, values-prod.yaml).
Helm also manages release history - every helm upgrade creates a new release revision, stored in Kubernetes Secrets or ConfigMaps. This enables instant rollback to previous versions (helm rollback myapp 3 reverts to revision 3) without redeploying from Git or CI/CD.
The Helm chart format is the de facto standard for packaging Kubernetes applications. Public chart repositories (Artifact Hub) provide thousands of pre-built charts for common applications (PostgreSQL, Redis, Prometheus, Nginx). Internal chart repositories enable sharing application charts across teams.
For the Kubernetes resources that Helm manages, see Kubernetes Best Practices. For packaging and distributing charts in CI/CD, see GitLab CI/CD Pipelines.
Core Principles
- DRY Principle: Define templates once, parameterize with values
- Environment Parity: Same chart for all environments, different values
- Versioning: Semantic versioning for charts and releases
- Rollback Safety: Test upgrades, maintain rollback capability
- Dependency Management: Declare chart dependencies explicitly
- Security: Never commit secrets in values files
- Testing: Validate charts with
helm lintandhelm test - Documentation: Maintain README and values documentation
Chart Structure
A Helm chart is a directory containing files that describe Kubernetes resources. Understanding chart structure is fundamental to creating maintainable, reusable charts.
Understanding Chart Components
Chart.yaml defines chart metadata: name, version, description, maintainers, and dependencies. The version field uses semantic versioning (major.minor.patch) and increments with each chart change. The appVersion field specifies the version of the application the chart deploys (e.g., chart version 1.2.3 might deploy application version 2.1.0).
values.yaml provides default configuration values. Users override these values at install time (helm install myapp ./chart -f values-prod.yaml or --set image.tag=2.1.0). Well-designed values files are documented with comments explaining each value's purpose and valid options.
templates/ contains Kubernetes manifest templates. These files use Go templating syntax ({{ .Values.image.repository }}) to inject values. The _helpers.tpl file defines reusable template functions (common labels, resource names) used across multiple templates.
charts/ stores dependency charts. When you declare dependencies in Chart.yaml, helm dependency update downloads them into this directory. Bundling dependencies ensures the chart is self-contained - users don't need to install dependencies separately.
NOTES.txt is a template that renders post-installation instructions. Helm displays this after successful installation, providing users with next steps (accessing the application URL, retrieving admin passwords, checking deployment status).
Chart Metadata (Chart.yaml)
The Chart.yaml file is the chart's manifest, describing the chart itself and its dependencies.
apiVersion: v2 # Helm 3 uses v2
name: payment-service
version: 1.2.3 # Chart version (SemVer)
appVersion: "2.1.0" # Application version being deployed
description: Payment processing service for banking platform
type: application # application or library
keywords:
- payment
- banking
- microservice
home: https://github.com/example/payment-service
sources:
- https://github.com/example/payment-service
maintainers:
- name: Platform Team
email: [email protected]
dependencies:
- name: postgresql
version: "12.1.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled # Install only if postgresql.enabled=true
- name: redis
version: "16.8.0"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
Version management: Increment version when chart templates or defaults change. Increment appVersion when deploying a new application version. Following semantic versioning:
- Patch (1.2.3 → 1.2.4): Bug fixes, minor template improvements, no breaking changes
- Minor (1.2.3 → 1.3.0): New features, new optional values, backward-compatible changes
- Major (1.2.3 → 2.0.0): Breaking changes requiring user intervention (renamed values, removed templates, changed defaults that affect behavior)
Dependencies: Declaring dependencies in Chart.yaml ensures reproducible deployments. When you run helm dependency update, Helm downloads exact versions specified, creating Chart.lock with checksums. This locks dependency versions preventing unexpected changes from upstream chart updates.
The condition field enables conditional dependency installation. Set postgresql.enabled: false to skip installing PostgreSQL (useful when using an external database). The alias field (not shown) allows installing the same chart multiple times with different configurations.
Values and Templating
Values parameterize charts, making them reusable across environments. Templates inject these values into Kubernetes manifests using Go template syntax.
Values File Structure
# values.yaml - Default configuration
replicaCount: 3
image:
repository: registry.example.com/payment-service
tag: "2.1.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
hosts:
- host: payments.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: payments-tls
hosts:
- payments.example.com
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
postgresql:
enabled: true
auth:
username: paymentuser
database: payments
# Password should be in secrets, not here
env:
- name: LOG_LEVEL
value: "INFO"
- name: FEATURE_FLAG_NEW_FLOW
value: "true"
Organizing values: Group related values under keys (image.*, service.*, resources.*). This creates logical structure and avoids naming conflicts. Use comments to document each value's purpose, valid options, and examples.
Environment-specific values: Create separate values files for each environment:
values.yaml: Defaults suitable for dev/testingvalues-prod.yaml: Production overrides (higher resources, replicas, enabled features)values-staging.yaml: Staging overrides
At deployment time: helm install payment-service ./chart -f values-prod.yaml merges values-prod.yaml over values.yaml.
Template Basics
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "payment-service.fullname" . }}
labels:
{{- include "payment-service.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "payment-service.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "payment-service.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 10 }}
{{- with .Values.env }}
env:
{{- toYaml . | nindent 8 }}
{{- end }}
Template syntax:
{{ .Values.key }}: Access values from values.yaml{{ .Chart.Name }}: Access Chart.yaml metadata{{ .Release.Name }}: Access release name (fromhelm install NAME){{- }}: Trim whitespace (prevents extra blank lines in output)| nindent 4: Pipe to function (indent by 4 spaces){{- if condition }}...{{- end }}: Conditional rendering{{- range .Values.list }}...{{- end }}: Loop over list
Helper templates (templates/_helpers.tpl):
{{/*
Common labels
*/}}
{{- define "payment-service.labels" -}}
helm.sh/chart: {{ include "payment-service.chart" . }}
{{ include "payment-service.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "payment-service.selectorLabels" -}}
app.kubernetes.io/name: {{ include "payment-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create a default fully qualified app name
*/}}
{{- define "payment-service.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 }}
Helper templates are reusable snippets that generate consistent output across all templates. The payment-service.labels helper generates standard Kubernetes labels. The payment-service.fullname helper creates resource names, handling release name prefixing and truncation (Kubernetes names limited to 63 characters).
Use helpers for:
- Common labels (
app.kubernetes.io/*labels) - Resource naming (combine release name and chart name)
- Complex conditionals (repeated logic across templates)
- Formatting (converting values to specific formats)
Dependencies and Subcharts
Helm charts can depend on other charts, enabling composition of complex applications from reusable components.
Declaring Dependencies
Dependencies are declared in Chart.yaml as shown earlier. After adding dependencies:
# Download dependencies
helm dependency update ./payment-service
# Creates:
# - charts/postgresql-12.1.0.tgz
# - charts/redis-16.8.0.tgz
# - Chart.lock
The Chart.lock file locks exact versions and checksums:
dependencies:
- name: postgresql
repository: https://charts.bitnami.com/bitnami
version: 12.1.0
digest: sha256:abc123...
- name: redis
repository: https://charts.bitnami.com/bitnami
version: 16.8.0
digest: sha256:def456...
generated: "2025-01-08T10:30:00Z"
Commit Chart.lock to version control to ensure all team members and CI/CD use identical dependency versions. Don't commit the charts/ directory - regenerate it from Chart.lock with helm dependency update.
Configuring Dependencies
Override dependency values in the parent chart's values.yaml:
# payment-service/values.yaml
postgresql:
enabled: true
auth:
username: paymentuser
database: payments
existingSecret: payment-db-secret # Reference external secret
primary:
persistence:
size: 20Gi
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
redis:
enabled: true
auth:
enabled: true
existingSecret: payment-redis-secret
master:
persistence:
size: 8Gi
resources:
requests:
cpu: 250m
memory: 256Mi
Values nested under postgresql.* pass to the PostgreSQL subchart. Values nested under redis.* pass to the Redis subchart. This enables configuring dependencies without modifying their charts.
Conditional dependencies: Use the condition field in Chart.yaml to enable/disable dependencies:
# Chart.yaml
dependencies:
- name: postgresql
version: "12.1.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
# values-external-db.yaml
postgresql:
enabled: false # Use external database instead
externalDatabase:
host: postgres.prod.example.com
port: 5432
username: paymentuser
database: payments
Deploy with external database: helm install myapp ./chart -f values-external-db.yaml
Versioning and Releases
Helm tracks every installation and upgrade as a release revision, enabling rollback and auditability.
Release Management
# Install release
helm install payment-service ./chart -f values-prod.yaml
# Creates release "payment-service" revision 1
# List releases
helm list -n banking
# Upgrade release
helm upgrade payment-service ./chart --set image.tag=2.2.0
# Creates revision 2
# View release history
helm history payment-service
# Rollback to previous revision
helm rollback payment-service 1
# Creates revision 3 (identical to revision 1)
# Uninstall release
helm uninstall payment-service
Release storage: Helm 3 stores release information in Kubernetes Secrets (or ConfigMaps) in the same namespace as the release. Each revision is stored separately, enabling rollback. Release secrets are named sh.helm.release.v1.RELEASE_NAME.vREVISION.
Atomic installs: Use --atomic flag to automatically rollback on failed install/upgrade:
helm upgrade payment-service ./chart --atomic --timeout 5m
If the upgrade fails (Pods don't become ready within 5 minutes), Helm automatically rolls back to the previous revision.
Helm Hooks
Hooks are Kubernetes resources that Helm executes at specific points in the release lifecycle. Use hooks for tasks like database migrations, schema updates, or cleanup jobs.
Hook Example: Database Migration
# templates/migration-hook.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "payment-service.fullname" . }}-migration
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migration
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./migrate"]
args: ["up"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: payment-db-secret
key: url
backoffLimit: 3
Hook annotations:
helm.sh/hook: Defines when the hook executes (pre-install,post-install,pre-upgrade,post-upgrade,pre-delete,post-delete,pre-rollback,post-rollback)helm.sh/hook-weight: Controls hook execution order (lower weight = earlier execution). Use negative weights for hooks that must run before regular resources, positive weights for hooks after resources.helm.sh/hook-delete-policy: Controls hook cleanup (before-hook-creationdeletes previous hook before creating new one,hook-succeededdeletes after success,hook-failedkeeps on failure for debugging)
Migration hook workflow:
- User runs
helm upgrade payment-service ./chart - Helm renders templates
- Helm applies pre-upgrade hooks (if any)
- Helm applies regular resources (Deployment, Service, etc.) with rolling update
- Helm applies post-upgrade hooks (migration Job)
- Migration Job runs, executing database schema changes
- If migration succeeds, Helm deletes the Job (due to
before-hook-creationpolicy) - Upgrade completes
Testing Charts
Helm provides tools for validating charts before deployment: linting, dry-run rendering, and test hooks.
# Lint chart for issues
helm lint ./payment-service
# Dry-run install (render templates without deploying)
helm install payment-service ./payment-service --dry-run --debug
# Template rendering (no Kubernetes API calls)
helm template payment-service ./payment-service -f values-prod.yaml
# Install with test hooks
helm install payment-service ./payment-service
helm test payment-service
Test Hooks
Test hooks are Kubernetes Pods that run tests against the deployed release:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "payment-service.fullname" . }}-test-connection"
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox:1.36
command: ['wget']
args: ['{{ include "payment-service.fullname" . }}:{{ .Values.service.port }}']
Running helm test payment-service creates this Pod, which attempts to connect to the payment-service. If the Pod succeeds (exit code 0), the test passes. If it fails, the test fails.
Common test patterns:
- HTTP health check (
curlorwgetthe service endpoint) - Database connectivity (connect and run simple query)
- Authentication verification (test login endpoint)
- Smoke tests (verify critical functionality)
Chart Repositories
Chart repositories store and distribute Helm charts. Public repositories (Artifact Hub, Bitnami charts) provide community charts. Private repositories enable sharing internal charts.
# Add repository
helm repo add bitnami https://charts.bitnami.com/bitnami
# Update repository index
helm repo update
# Search for charts
helm search repo postgresql
# Install from repository
helm install my-postgres bitnami/postgresql --version 12.1.0
Hosting a Chart Repository
Chart repositories are HTTP servers serving an index.yaml file and packaged charts (.tgz files). You can host repositories on:
- GitHub Pages (static site)
- Cloud storage (S3, GCS, Azure Blob) with HTTP access
- ChartMuseum (dedicated chart repository server)
- Artifact registries (Harbor, Nexus, JFrog Artifactory)
Creating a repository:
# Package chart
helm package ./payment-service
# Creates payment-service-1.2.3.tgz
# Create or update repository index
helm repo index . --url https://charts.example.com
# Upload index.yaml and .tgz files to HTTP server
The index.yaml file lists all charts and versions:
apiVersion: v1
entries:
payment-service:
- name: payment-service
version: 1.2.3
appVersion: "2.1.0"
description: Payment processing service
urls:
- https://charts.example.com/payment-service-1.2.3.tgz
created: "2025-01-08T10:30:00Z"
digest: sha256:abc123...
Users add your repository and install charts:
helm repo add example https://charts.example.com
helm repo update
helm install myapp example/payment-service
Best Practices
Security
Never commit secrets in values files. Use external secret management:
- External Secrets Operator syncing from Vault
- Sealed Secrets encrypted with cluster public key
- CI/CD variables injected at deployment (
--set dbPassword=$DB_PASSWORD) - Kubernetes Secrets created separately, referenced in charts
Use .helmignore to exclude files from packaged charts:
# .helmignore
.git/
.gitignore
.DS_Store
*.md
docs/
examples/
tests/
.gitlab-ci.yml
Documentation
README.md in chart directory documents:
- Chart purpose and features
- Prerequisites (Kubernetes version, required CRDs)
- Installation instructions
- Configuration options (table of values with descriptions, defaults, and examples)
- Upgrading guidelines
- Troubleshooting common issues
values.yaml comments document every configuration option:
# Number of replicas (minimum 3 for high availability in production)
replicaCount: 3
image:
# Container image repository
repository: registry.example.com/payment-service
# Image pull policy (IfNotPresent, Always, Never)
pullPolicy: IfNotPresent
# Image tag (overrides Chart appVersion)
tag: ""
Version Management
Follow semantic versioning for chart versions:
- Breaking changes: Increment major version (1.x.x → 2.0.0)
- New features: Increment minor version (1.2.x → 1.3.0)
- Bug fixes: Increment patch version (1.2.3 → 1.2.4)
Maintain CHANGELOG.md documenting changes in each version:
# Changelog
## [1.3.0] - 2025-01-08
### Added
- Support for horizontal pod autoscaling
- Liveness and readiness probes configuration
### Changed
- Updated default resource requests (500m → 1000m CPU)
### Deprecated
- `service.enabled` value (service always created now)
## [1.2.3] - 2025-01-01
### Fixed
- Fixed ingress annotation for rate limiting
Further Reading
Internal Documentation
- Kubernetes Best Practices - Kubernetes resource definitions
- Docker Best Practices - Container image preparation
- GitLab CI/CD Pipelines - Helm in CI/CD workflows
- Secrets Management - Managing sensitive configuration
- Terraform Infrastructure as Code - Infrastructure provisioning
External Resources
- Helm Documentation
- Helm Chart Best Practices
- Artifact Hub - Public chart repository
- Helm Chart Template Guide
Summary
Key Takeaways
- Chart structure - Chart.yaml for metadata, values.yaml for configuration, templates/ for K8s resources
- Templating - Go templates with .Values, .Chart, .Release; helper templates for reusable snippets
- Dependencies - Declare in Chart.yaml, configure via parent values, lock versions with Chart.lock
- Versioning - SemVer for charts, appVersion for application, track releases with revision history
- Hooks - pre/post-install/upgrade hooks for migrations, tests, cleanup
- Testing -
helm lint,helm template,helm testfor validation - Repositories - Distribute charts via HTTP repositories, index.yaml lists versions
- Security - Never commit secrets, use external secret management, .helmignore sensitive files
- Documentation - README and commented values.yaml for user guidance
- Best practices - DRY templates, semantic versioning, atomic upgrades, rollback safety
Next Steps: Review Kubernetes Best Practices for the resources Helm manages, GitLab CI/CD Pipelines for automating Helm deployments, and Secrets Management for handling sensitive configuration securely.