Helm Charts

A comprehensive guide to Helm Charts covering package management, templating, chart development, repository management, versioning, and best practices for packaging Kubernetes applications.

Helm Package Management Repositories Kubernetes Native
What is Helm?

Helm is the package manager for Kubernetes. It helps you define, install, and upgrade complex Kubernetes applications. Helm charts are packages of pre-configured Kubernetes resources that can be versioned, shared, and reused.

Helm solves the challenge of deploying complex microservices applications that consist of multiple interdependent Kubernetes resources. Instead of managing dozens of YAML files manually, you can use a single chart with configurable parameters.

Chart Values Template Engine Kubernetes Manifests Release
Key Helm Concepts:
  • Chart: A package of Kubernetes resources (templates + metadata)
  • Release: An instance of a chart running in a Kubernetes cluster
  • Repository: A collection of packaged charts (like a registry for charts)
  • Values: Configuration parameters that customize chart deployment
  • Template: A file that defines Kubernetes resources using Go templates
Helm Architecture

Helm consists of two main components: the Helm client (CLI) and the Tiller server (deprecated in Helm v3). In Helm v3, Tiller was removed, making Helm more secure and simpler.

Helm CLI

Client-side tool
The command-line interface for interacting with Helm. Used for chart creation, installation, upgrades, and repository management. Communicates directly with the Kubernetes API.
Chart operations

Chart Storage

Release storage in Kubernetes
Helm v3 stores release information as Kubernetes Secrets in the cluster namespace. This eliminates the need for Tiller and improves security.
Release management

Render Engine

Go template processing
Helm uses Go templates with custom functions and pipelines to render Kubernetes manifests from chart templates and values files.
Manifest generation

Chart Repository

Chart storage and distribution
OCI-compliant repositories for storing and sharing charts. Supports HTTP/HTTPS and OCI registry protocols.
Chart sharing
# Install Helm v3 curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh # Verify installation helm version # Add Helm repositories helm repo add stable https://charts.helm.sh/stable helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update # Search for charts helm search repo nginx helm search repo --versions nginx # Install a chart helm install my-nginx bitnami/nginx --namespace default # List releases helm list helm list --all-namespaces # Uninstall a release helm uninstall my-nginx
Helm v3 vs v2: Helm v3 removed Tiller for better security and simplicity. Always use Helm v3 for new projects. The command syntax is similar, but the architecture is significantly different.
Helm Chart Structure

A Helm chart follows a specific directory structure. Understanding this structure is essential for chart development.

# Chart Directory Structure my-app/ ├── Chart.yaml # Chart metadata (name, version, description) ├── values.yaml # Default configuration values ├── values-*.yaml # Environment-specific values (optional) ├── charts/ # Dependent charts (subcharts) ├── crds/ # Custom Resource Definitions ├── templates/ # Kubernetes resource templates │ ├── _helpers.tpl # Template helper functions │ ├── deployment.yaml # Deployment manifest template │ ├── service.yaml # Service manifest template │ ├── ingress.yaml # Ingress manifest template │ ├── configmap.yaml # ConfigMap template │ ├── secret.yaml # Secret template (not recommended) │ ├── hpa.yaml # HorizontalPodAutoscaler template │ ├── NOTES.txt # Post-installation notes │ └── tests/ # Test resources └── README.md # Documentation # Chart.yaml Example apiVersion: v2 name: my-app description: A Helm chart for my application type: application version: 1.2.3 # Chart version (semver) appVersion: 2.5.0 # Application version maintainers: - name: John Doe email: john@example.com - name: Jane Doe email: jane@example.com home: https://example.com icon: https://example.com/icon.png keywords: - web - microservice sources: - https://github.com/example/my-app dependencies: - name: postgresql version: 11.x.x repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled
Chart Versioning: Use semantic versioning (MAJOR.MINOR.PATCH) for chart versions. The appVersion field indicates the version of the application being packaged, which may differ from the chart version.
Helm Templating: The Power of Go Templates

Helm uses Go templates with additional functions and pipelines to generate Kubernetes manifests dynamically. This enables configuration-driven deployments with maximum flexibility.

Template Directives

Control flow and expressions
Use {{ }} for actions, {{ . }} for the root object, {{ .Values }} for values, and {{ .Release }} for release metadata.
Dynamic content

Template Functions

Built-in and custom functions
Functions like include, default, required, toYaml, fromYaml, and tpl for text processing.
Data transformation

Sprig Functions

Extended template functions
Sprig provides over 60 additional functions for string manipulation, math operations, date formatting, and more.
Advanced templating

Security Context

Template safety
Helm templates are sandboxed and cannot access the file system or network. This prevents malicious templates from compromising the system.
Secure templating
# _helpers.tpl - Template functions {{- define "my-app.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{- 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 }} {{- define "my-app.labels" -}} helm.sh/chart: {{ include "my-app.name" . }}-{{ .Chart.Version | replace "+" "_" }} {{ include "my-app.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-app.fullname" . }} labels: {{- include "my-app.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} 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: - containerPort: {{ .Values.service.port }} env: - name: ENVIRONMENT value: {{ .Values.environment | default "production" | quote }} {{- with .Values.env }} {{- toYaml . | nindent 8 }} {{- end }} resources: {{- toYaml .Values.resources | nindent 10 }} {{- with .Values.livenessProbe }} livenessProbe: {{- toYaml . | nindent 10 }} {{- end }} {{- with .Values.readinessProbe }} readinessProbe: {{- toYaml . | nindent 10 }} {{- end }}
# Advanced Templating Examples # Conditional blocks {{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ include "my-app.fullname" . }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} 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 }} # Range loops {{- range $key, $value := .Values.configMap }} {{ $key }}: {{ $value | quote }} {{- end }} # with statement for optional fields {{- with .Values.resources }} resources: {{- toYaml . | nindent 2 }} {{- end }} # Required values {{- required "A valid .Values.service.type is required" .Values.service.type }} # Default values {{- .Values.image.pullPolicy | default "IfNotPresent" }} # Include and indent {{ include "my-app.labels" . | indent 4 }} # Sprig functions {{- .Values.image.repository | default "nginx" | quote }} {{- .Values.replicaCount | int | add 1 }} {{- .Values.env | toYaml | nindent 4 }}
Values Management: Configuration at Scale

Helm values provide a powerful way to configure chart behavior across different environments. Understanding values management is essential for production deployments.

# values.yaml (default configuration) replicaCount: 1 image: repository: nginx tag: latest pullPolicy: IfNotPresent service: type: ClusterIP port: 80 ingress: enabled: false className: "" annotations: {} hosts: [] resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" environment: production configMap: key1: value1 key2: value2 secrets: key1: base64-encoded-value env: - name: LOG_LEVEL value: info # values-dev.yaml (development override) replicaCount: 1 image: tag: dev environment: development ingress: enabled: true hosts: - host: dev.example.com paths: - path: / pathType: Prefix # values-prod.yaml (production override) replicaCount: 3 image: tag: latest environment: production resources: requests: memory: "256Mi" cpu: "500m" limits: memory: "512Mi" cpu: "1000m" ingress: enabled: true annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod hosts: - host: app.example.com paths: - path: / pathType: Prefix # Install with values helm install my-app ./my-app -f values-dev.yaml helm upgrade my-app ./my-app -f values-prod.yaml # Set individual values helm install my-app ./my-app --set image.tag=2.0.0 --set replicaCount=5 # Use multiple values files helm install my-app ./my-app -f values-base.yaml -f values-prod.yaml
Values Best Practices:
  • Use environment-specific values files (values-dev.yaml, values-prod.yaml)
  • Keep default values in values.yaml (apply to all environments)
  • Use --set for overriding individual values during CI/CD
  • Validate values with schema validation (values.schema.json)
  • Document all configurable values in README.md
Chart Development Workflow

Developing Helm charts requires a systematic workflow for creation, testing, and validation.

# Create a new chart helm create my-chart # Dry run to preview rendered manifests helm install my-release ./my-chart --dry-run --debug # Render templates only helm template my-chart ./my-chart --debug # Lint chart for best practices helm lint ./my-chart # Test chart installation in a namespace kubectl create namespace test helm install test-release ./my-chart --namespace test --set environment=test # Run chart tests (if defined in templates/tests/) helm test test-release # Package chart for distribution helm package ./my-chart # Check chart dependencies helm dependency list ./my-chart helm dependency update ./my-chart # Unit tests for templates # Using helm-unittest plugin helm plugin install https://github.com/helm-unittest/helm-unittest helm unittest ./my-chart # Example test file (templates/tests/deployment-test.yaml) --- apiVersion: v1 kind: Pod metadata: name: "{{ include "my-app.fullname" . }}-test-connection" labels: {{- include "my-app.labels" . | nindent 4 }} annotations: "helm.sh/hook": test-success spec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never
Chart Development Best Practices:
  • Always run helm lint before packaging
  • Use helm install --dry-run --debug to validate templates
  • Write unit tests for critical templates
  • Use helm test for integration testing
  • Version charts using semantic versioning
  • Keep charts small and focused (single responsibility)
  • Use dependency management for complex applications
Helm Repository Management

Helm repositories store and distribute charts. They can be hosted on HTTP servers, cloud storage, or OCI registries.

# Add a repository helm repo add bitnami https://charts.bitnami.com/bitnami helm repo add prometheus-community https://prometheus-community.github.io/helm-charts # List repositories helm repo list # Update repository index helm repo update # Search repositories helm search repo bitnami/nginx helm search repo --versions nginx # Remove a repository helm repo remove bitnami # Package and push to repository (using ChartMuseum) helm package ./my-chart curl --data-binary "@my-chart-1.2.3.tgz" http://chartmuseum-server/api/charts # OCI Registry (Helm v3.7+) helm registry login registry.example.com helm push my-chart-1.2.3.tgz oci://registry.example.com/helm-charts # Install from OCI registry helm install my-release oci://registry.example.com/helm-charts/my-chart --version 1.2.3 # Create a repository index helm repo index ./my-repo --url https://example.com/charts # Using GitHub Pages as a Helm repo # Create gh-pages branch # Package and index charts # Upload to gh-pages branch # Access via https://username.github.io/repo-name/
Repository Security: Always use HTTPS for chart repositories. For OCI registries, enable authentication and authorization. Consider using artifact signing (Helm provenance) for supply chain security.
Helm Best Practices

Chart Packaging

Keep charts focused
Each chart should represent a single, reusable component or microservice. Use dependencies for complex applications.
Modular design

Templating

Use _helpers.tpl
Define reusable template functions in _helpers.tpl. Use include instead of template for better scoping.
Clean templates

Security

No secrets in charts
Never store secrets in charts. Use external secret management (Sealed Secrets, Vault) or provide values at install time.
Secure deployments

Versioning

Semantic versioning
Follow semantic versioning for chart versions. Increment major for breaking changes, minor for new features, patch for bug fixes.
Version management

Values Organization

Structured values
Organize values logically. Use nested structures for related settings. Document all values in README.md.
Configuration management

Testing

Test your charts
Use helm unittest for unit testing, helm test for integration testing, and helm lint for validation.
Quality assurance
# values.schema.json - JSON Schema Validation { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "replicaCount": { "type": "integer", "minimum": 1, "maximum": 10 }, "image": { "type": "object", "properties": { "repository": { "type": "string" }, "tag": { "type": "string" }, "pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] } }, "required": ["repository", "tag"] }, "service": { "type": "object", "properties": { "type": { "type": "string", "enum": ["ClusterIP", "NodePort", "LoadBalancer"] }, "port": { "type": "integer", "minimum": 1, "maximum": 65535 } }, "required": ["type", "port"] }, "resources": { "type": "object", "properties": { "requests": { "type": "object", "properties": { "memory": { "type": "string" }, "cpu": { "type": "string" } } }, "limits": { "type": "object", "properties": { "memory": { "type": "string" }, "cpu": { "type": "string" } } } } } }, "required": ["image", "service"] }
Frequently Asked Questions
What is the difference between Helm and Kustomize?
Helm is a package manager with templating and dependencies management. Kustomize is a configuration management tool that applies transformations to YAML without templating. Helm is better for packaging and sharing reusable components; Kustomize is simpler for configuration overlays in GitOps workflows.
How do I manage secrets in Helm?
Never store secrets in charts directly. Use Sealed Secrets (encrypt secrets in Git), Bitnami's Secrets Manager, HashiCorp Vault, or external secret management tools. Provide secrets as values during installation via environment variables or secrets files.
Can I use Helm with GitOps?
Yes! ArgoCD and Flux both support Helm. ArgoCD can directly use Helm charts as sources and can render charts using Helm's template engine. This combines the power of Helm packaging with GitOps automation.
What is a Helm hook?
Hooks allow you to run jobs or pods during specific phases of a release lifecycle (pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete). They're useful for database migrations, schema updates, and initialization tasks.
How do I rollback a Helm release?
Use helm history <release-name> to see revision history, then helm rollback <release-name> <revision> to rollback. Helm stores release history in Kubernetes Secrets.
What are the best practices for chart versioning?
Follow semantic versioning (MAJOR.MINOR.PATCH). Increment major for incompatible changes, minor for new features, and patch for bug fixes. The chart version is independent of the appVersion (application version).
How do I debug Helm template rendering?
Use helm install --dry-run --debug to see rendered manifests with debugging info. Use helm template --debug for template-only rendering. Use --set to test different value combinations.
What is the recommended way to manage multiple environments with Helm?
Use environment-specific values files (values-dev.yaml, values-staging.yaml, values-prod.yaml). Combine with --set for CI/CD overrides. Use Helm charts with ArgoCD or Flux for GitOps-based environment management.
Previous: GitOps with ArgoCD Next: Kubernetes Networking

Helm is the industry standard for Kubernetes package management. Master Helm charts to streamline your Kubernetes deployments, reduce complexity, and enable reusable infrastructure patterns.