Helm Charts

The Package Manager for Kubernetes - Complete Guide

🚀 Introduction to Helm

What is Helm?

Helm is the package manager for Kubernetes, often called the "apt/yum/homebrew" for Kubernetes. It simplifies the deployment and management of applications on Kubernetes clusters.

Why Use Helm?

📦 Package Management

Bundle your K8s manifests into reusable packages called Charts

🔧 Configuration Management

Separate configuration from templates using values files

📋 Version Control

Track releases and easily rollback to previous versions

🔄 Reusability

Share charts publicly or within your organization

🏗️ Dependency Management

Manage complex applications with multiple dependencies

🎯 Templating

Powerful templating engine for dynamic configurations

Helm Architecture

Helm 3 Architecture (Current)

  • Helm Client: Command-line tool that interacts with Kubernetes API
  • No Tiller: Helm 3 removed Tiller (server component) for better security
  • Release State: Stored as Kubernetes secrets in the namespace
  • 3-way Strategic Merge: Better upgrade process comparing old manifest, new manifest, and live state

💻 Installation & Setup

Installing Helm

macOS

Terminal
# Using Homebrew
brew install helm

# Using MacPorts
sudo port install helm-3.0

Linux

Terminal
# Using snap
sudo snap install helm --classic

# Using script
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Using apt (Debian/Ubuntu)
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

Windows

PowerShell
# Using Chocolatey
choco install kubernetes-helm

# Using Scoop
scoop install helm

Verify Installation

Terminal
# Check Helm version
helm version

# Output example:
version.BuildInfo{Version:"v3.13.0", GitCommit:"825e86f6", GitTreeState:"clean", GoVersion:"go1.20.8"}

Initial Setup

Terminal
# Add the official Helm stable charts repo
helm repo add stable https://charts.helm.sh/stable

# Add Bitnami repo (popular charts)
helm repo add bitnami https://charts.bitnami.com/bitnami

# Update repo information
helm repo update

# List added repositories
helm repo list

📚 Core Concepts

📊 Chart

A Helm package containing all resource definitions necessary to run an application, tool, or service inside Kubernetes

  • Collection of YAML templates
  • Default configuration values
  • Dependencies on other charts
  • Metadata about the package

🚀 Release

An instance of a chart running in a Kubernetes cluster

  • Unique name within namespace
  • Can have multiple releases of same chart
  • Tracks deployment history
  • Enables rollback capability

📁 Repository

Place where charts can be collected and shared

  • HTTP server hosting index.yaml
  • Can be public or private
  • Similar to Docker Hub
  • Supports authentication

⚙️ Values

Configuration data that can be passed to charts

  • Override default configurations
  • Environment-specific settings
  • Can be provided via files or CLI
  • Support complex data structures

📝 Templates

Go template files that generate Kubernetes manifests

  • Dynamic resource generation
  • Conditional logic support
  • Loops and iterations
  • Built-in and custom functions

🔌 Hooks

Allow chart developers to intervene at certain points in release lifecycle

  • Pre/post install
  • Pre/post upgrade
  • Pre/post rollback
  • Test hooks

Helm Workflow

1
Create/Find Chart
2
Configure Values
3
Install Release
4
Manage Lifecycle

📁 Chart Structure

Directory Structure

📁 mychart/
📄 Chart.yaml (required)
📄 values.yaml
📄 values.schema.json
📁 charts/
📁 crds/
📁 templates/
📄 deployment.yaml
📄 service.yaml
📄 ingress.yaml
📄 _helpers.tpl
📄 NOTES.txt
📄 .helmignore
📄 LICENSE
📄 README.md

File Descriptions

Chart.yaml

Chart.yaml
apiVersion: v2  # Chart API version (required)
name: mychart     # Chart name (required)
version: 0.1.0    # SemVer 2 version (required)

# Optional fields
kubeVersion: ">=1.19.0-0"  # Kubernetes version constraint
description: A Helm chart for my awesome application
type: application  # application or library
keywords:
  - web
  - application
home: https://example.com
sources:
  - https://github.com/example/mychart
dependencies:      # Chart dependencies
  - name: nginx
    version: 1.2.3
    repository: https://example.com/charts
    condition: nginx.enabled
    tags:
      - front-end
    import-values:
      - data
maintainers:       # Chart maintainers
  - name: John Doe
    email: john@example.com
    url: https://example.com
icon: https://example.com/icon.png
appVersion: "1.16.0"  # Version of the app
deprecated: false
annotations:
  example: A chart annotation

values.yaml

values.yaml
# Default values for mychart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
  repository: nginx
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: ""

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  name: ""

podAnnotations: {}

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  className: ""
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls: []

resources: {}
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80

nodeSelector: {}

tolerations: []

affinity: {}

values.schema.json

values.schema.json
{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "properties": {
    "replicaCount": {
      "description": "Number of replicas",
      "type": "integer",
      "minimum": 1
    },
    "image": {
      "description": "Container Image",
      "type": "object",
      "properties": {
        "repository": {
          "type": "string",
          "pattern": "^[a-z0-9-_/]+$"
        },
        "tag": {
          "type": "string"
        },
        "pullPolicy": {
          "type": "string",
          "enum": ["Always", "IfNotPresent", "Never"]
        }
      }
    }
  }
}

.helmignore

.helmignore
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

📝 Working with Templates

Template Basics

Helm uses the Go template language with additional functions. Templates are stored in the templates/ directory.

Basic Template Example

templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "mychart.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "mychart.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          {{- if .Values.env }}
          env:
            {{- range $key, $value := .Values.env }}
            - name: {{ $key }}
              value: {{ $value | quote }}
            {{- end }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

Template Controls

Conditionals

Conditional Examples
# Simple if statement
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ... ingress configuration ...
{{- end }}

# If-else statement
replicas: {{ if .Values.autoscaling.enabled }}{{ .Values.autoscaling.minReplicas }}{{ else }}{{ .Values.replicaCount }}{{ end }}

# Multiple conditions
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
apiVersion: v1
kind: PersistentVolumeClaim
# ... PVC configuration ...
{{- end }}

# Checking for empty/nil values
{{- if .Values.nodeSelector }}
nodeSelector:
  {{- toYaml .Values.nodeSelector | nindent 2 }}
{{- end }}

Loops

Loop Examples
# Simple range loop
env:
{{- range $key, $value := .Values.env }}
  - name: {{ $key }}
    value: {{ $value | quote }}
{{- end }}

# Loop with index
ports:
{{- range $index, $port := .Values.service.ports }}
  - name: port-{{ $index }}
    port: {{ $port }}
    targetPort: {{ $port }}
{{- end }}

# Loop over list of objects
{{- range .Values.ingress.hosts }}
  - host: {{ .host | quote }}
    http:
      paths:
      {{- range .paths }}
        - path: {{ .path }}
          pathType: {{ .pathType }}
          backend:
            service:
              name: {{ $.Release.Name }}
              port:
                number: {{ $.Values.service.port }}
      {{- end }}
{{- end }}

Variables

Variable Examples
# Define and use variables
{{- $fullName := include "mychart.fullname" . -}}
{{- $labels := include "mychart.labels" . -}}
metadata:
  name: {{ $fullName }}
  labels:
    {{- $labels | nindent 4 }}

# Variables in range loops
{{- range $key, $value := .Values.secrets }}
{{- $secretName := printf "%s-%s" $.Release.Name $key }}
---
apiVersion: v1
kind: Secret
metadata:
  name: {{ $secretName }}
data:
  {{- range $k, $v := $value }}
  {{ $k }}: {{ $v | b64enc | quote }}
  {{- end }}
{{- end }}

Template Functions (_helpers.tpl)

templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "mychart.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 }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mychart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ include "mychart.chart" . }}
{{ include "mychart.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "mychart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "mychart.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

Interactive Template Demo

Try Template Rendering

Template
Values
Click "Render Template" to see the result...

🔧 Template Functions

Built-in Functions

String Functions

Text Manipulation

{{ .Values.name | upper }}     # MYAPP
{{ .Values.name | lower }}     # myapp
{{ .Values.name | title }}     # Myapp
{{ .Values.name | trim }}      # Remove whitespace
{{ .Values.name | trimPrefix "my" }}
{{ .Values.name | trimSuffix "app" }}
{{ .Values.name | replace "a" "b" }}
{{ .Values.name | nospace }}   # Remove all spaces

String Operations

{{ .Values.name | quote }}     # "myapp"
{{ .Values.name | squote }}    # 'myapp'
{{ .Values.name | indent 4 }}  # Indent 4 spaces
{{ .Values.name | nindent 4 }} # Newline + indent
{{ printf "%s-%s" .Release.Name .Chart.Name }}
{{ .Values.name | substr 0 5 }}
{{ .Values.name | trunc 63 }}  # Truncate to 63 chars

Type Conversion Functions

Type Conversions
{{ .Values.port | int }}        # Convert to integer
{{ .Values.enabled | toString }} # Convert to string
{{ .Values.data | toJson }}     # Convert to JSON
{{ .Values.data | toPrettyJson }} # Pretty JSON
{{ .Values.data | toYaml }}     # Convert to YAML
{{ .Values.items | toStrings }} # Convert list items to strings
{{ .Values.number | float64 }}  # Convert to float
{{ .Values.config | fromYaml }} # Parse YAML string
{{ .Values.config | fromJson }} # Parse JSON string

List Functions

List Operations
{{ list "a" "b" "c" }}          # Create a list
{{ .Values.items | first }}     # Get first item
{{ .Values.items | last }}      # Get last item
{{ .Values.items | rest }}      # All except first
{{ .Values.items | initial }}   # All except last
{{ .Values.items | append "d" }} # Add to end
{{ .Values.items | prepend "z" }} # Add to beginning
{{ .Values.items | reverse }}   # Reverse order
{{ .Values.items | uniq }}      # Remove duplicates
{{ .Values.items | sort }}      # Sort list
{{ has "item" .Values.items }}  # Check if contains
{{ .Values.items | join "," }}  # Join with separator

Dictionary Functions

Dictionary Operations
{{ dict "key1" "value1" "key2" "value2" }}  # Create dict
{{ .Values.config | keys }}     # Get all keys
{{ .Values.config | values }}   # Get all values
{{ hasKey .Values.config "key" }} # Check if key exists
{{ get .Values.config "key" }}  # Get value by key
{{ set .Values.config "key" "value" }} # Set key-value
{{ unset .Values.config "key" }} # Remove key
{{ merge .Values.config .Values.defaults }} # Merge dicts
{{ pick .Values.config "key1" "key2" }} # Pick keys
{{ omit .Values.config "key1" "key2" }} # Omit keys

Math Functions

Math Operations
{{ add 1 2 }}                   # 3
{{ sub 5 3 }}                   # 2
{{ mul 2 3 }}                   # 6
{{ div 10 2 }}                  # 5
{{ mod 10 3 }}                  # 1
{{ max 1 2 3 }}                 # 3
{{ min 1 2 3 }}                 # 1
{{ floor 3.14 }}                # 3
{{ ceil 3.14 }}                 # 4
{{ round 3.14159 2 }}           # 3.14

Encoding Functions

Encoding Operations
{{ .Values.secret | b64enc }}   # Base64 encode
{{ .Values.encoded | b64dec }}  # Base64 decode
{{ .Values.data | sha256sum }}  # SHA256 hash
{{ .Values.data | sha1sum }}    # SHA1 hash
{{ .Values.url | urlquery }}    # URL encode
{{ .Values.url | urlunquery }}  # URL decode

Flow Control Functions

Flow Control
# Default values
{{ .Values.name | default "myapp" }}
{{ default "default" .Values.undefined }}

# Ternary operator
{{ ternary "prod" "dev" .Values.production }}

# Coalesce - first non-empty value
{{ coalesce .Values.name .Values.nameOverride "default" }}

# Empty check
{{ if empty .Values.config }}
  # Config is empty or not set
{{ end }}

# Required - fail if not set
{{ required "A valid .Values.database entry is required!" .Values.database }}

# Fail with message
{{ fail "This chart requires Kubernetes 1.20+" }}

Lookup Function

Lookup Examples
# Look up existing resources
{{ $svc := lookup "v1" "Service" .Release.Namespace "myservice" }}
{{ if $svc }}
  # Service exists, use its ClusterIP
  serviceIP: {{ $svc.spec.clusterIP }}
{{ end }}

# List all services in namespace
{{ $services := lookup "v1" "Service" .Release.Namespace "" }}
{{ range $services.items }}
  - {{ .metadata.name }}
{{ end }}

# Check if resource exists
{{ if lookup "v1" "ConfigMap" .Release.Namespace "my-config" }}
  # ConfigMap exists
{{ else }}
  # ConfigMap doesn't exist
{{ end }}

🔗 Chart Dependencies

Defining Dependencies

Chart.yaml with dependencies
apiVersion: v2
name: myapp
version: 1.0.0
dependencies:
  - name: mysql
    version: 9.3.4
    repository: https://charts.bitnami.com/bitnami
    condition: mysql.enabled
    tags:
      - database
    import-values:
      - child: auth.rootPassword
        parent: mysqlPassword
  
  - name: redis
    version: ~17.3.0  # Version range
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled
    tags:
      - cache
  
  - name: common
    version: 1.x.x
    repository: file://../common  # Local chart

Managing Dependencies

Dependency Commands
# Download dependencies to charts/ directory
helm dependency update ./mychart

# List dependencies
helm dependency list ./mychart

# Build dependencies from Chart.lock
helm dependency build ./mychart

# Update specific dependency
helm dependency update ./mychart --skip-refresh

Configuring Dependencies

values.yaml for dependencies
# Enable/disable dependencies
mysql:
  enabled: true
  auth:
    rootPassword: "secretpassword"
    database: "myapp"
  primary:
    persistence:
      enabled: true
      size: 10Gi

redis:
  enabled: false
  architecture: standalone
  auth:
    enabled: false

# Global values accessible to all subcharts
global:
  storageClass: "fast-ssd"
  postgresql:
    auth:
      postgresPassword: "globalpassword"

Conditional Dependencies

Using Tags and Conditions
# In Chart.yaml
dependencies:
  - name: mysql
    condition: mysql.enabled
    tags:
      - database
      - sql
  
  - name: postgresql
    condition: postgresql.enabled
    tags:
      - database
      - sql

# In values.yaml
tags:
  database: false  # Disable all with 'database' tag

mysql:
  enabled: true  # Override tag setting

postgresql:
  enabled: false

🪝 Helm Hooks

Hook Types

Pre-install

Executes after templates are rendered, but before any resources are created

Post-install

Executes after all resources are loaded into Kubernetes

Pre-delete

Executes on a deletion request before any resources are deleted

Post-delete

Executes on a deletion request after all resources have been deleted

Pre-upgrade

Executes on an upgrade request after templates are rendered

Post-upgrade

Executes on an upgrade after all resources have been upgraded

Pre-rollback

Executes on a rollback request after templates are rendered

Post-rollback

Executes on a rollback request after all resources have been modified

Test

Executes when the Helm test subcommand is invoked

Hook Examples

Database Migration Hook

templates/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "myapp.fullname" . }}-db-migration
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    metadata:
      name: {{ include "myapp.fullname" . }}-db-migration
    spec:
      restartPolicy: Never
      containers:
      - name: db-migrate
        image: "{{ .Values.migration.image }}:{{ .Values.migration.tag }}"
        command: ["/bin/sh"]
        args:
          - "-c"
          - "python manage.py migrate --noinput"
        env:
        - name: DATABASE_URL
          value: {{ .Values.database.url | quote }}

Test Hook

templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: {{ include "myapp.fullname" . }}-test-connection
  annotations:
    "helm.sh/hook": test
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  containers:
  - name: test
    image: busybox
    command: ['wget']
    args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never

Hook Annotations

Hook Annotations Reference

  • helm.sh/hook - Specifies the hook type(s)
  • helm.sh/hook-weight - Defines execution order (ascending)
  • helm.sh/hook-delete-policy - When to delete hook resources:
    • before-hook-creation - Delete before new hook is launched
    • hook-succeeded - Delete after hook succeeds
    • hook-failed - Delete if hook fails

📦 Chart Repositories

Working with Repositories

Repository Management

Repository Commands
# Add a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add stable https://charts.helm.sh/stable
helm repo add prometheus https://prometheus-community.github.io/helm-charts

# Add repository with authentication
helm repo add private https://charts.example.com \
  --username myuser \
  --password mypass

# List repositories
helm repo list

# Update repositories
helm repo update

# Remove a repository
helm repo remove bitnami

# Search charts in repositories
helm search repo nginx
helm search repo bitnami/mysql --versions

Creating Your Own Repository

Generate Repository Index

Creating a Repository
# Package your charts
helm package ./mychart
helm package ./anotherchart

# Generate index.yaml
helm repo index . --url https://charts.example.com

# Update existing index
helm repo index . --url https://charts.example.com --merge index.yaml

Repository Structure

index.yaml
apiVersion: v1
entries:
  mychart:
  - apiVersion: v2
    appVersion: 1.16.0
    created: "2023-10-01T10:00:00.000Z"
    description: A Helm chart for Kubernetes
    digest: sha256:1234567890abcdef
    name: mychart
    type: application
    urls:
    - https://charts.example.com/mychart-0.1.0.tgz
    version: 0.1.0
generated: "2023-10-01T10:00:00.000Z"

Hosting Options

GitHub Pages

Free hosting for public charts using GitHub Pages

# In your gh-pages branch
helm package ./mychart
helm repo index . --url https://username.github.io/charts
git add .
git commit -m "Update charts"
git push origin gh-pages

ChartMuseum

Open-source Helm Chart Repository server

# Install ChartMuseum
helm install chartmuseum stable/chartmuseum \
  --set env.open.DISABLE_API=false \
  --set persistence.enabled=true

Harbor

Enterprise-class registry for containers and Helm charts

Artifactory

Universal repository manager with Helm support

🚀 Advanced Topics

Library Charts

Library Chart Definition
# Chart.yaml for library chart
apiVersion: v2
name: common
description: A Library Helm Chart for grouping common logic
type: library  # This makes it a library chart
version: 1.0.0

# templates/_deployment.tpl
{{- define "common.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "common.fullname" . }}
  labels:
    {{- include "common.labels" . | nindent 4 }}
spec:
  # ... common deployment spec ...
{{- end -}}

# Using in another chart
{{- include "common.deployment" . }}

Schema Validation

values.schema.json
{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["image", "service"],
  "properties": {
    "replicaCount": {
      "type": "integer",
      "minimum": 1,
      "maximum": 10
    },
    "image": {
      "type": "object",
      "required": ["repository", "tag"],
      "properties": {
        "repository": {
          "type": "string",
          "pattern": "^[a-z0-9-_/]+$"
        },
        "tag": {
          "type": "string",
          "pattern": "^[a-z0-9.-]+$"
        }
      }
    },
    "service": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": ["ClusterIP", "NodePort", "LoadBalancer"]
        },
        "port": {
          "type": "integer",
          "minimum": 1,
          "maximum": 65535
        }
      }
    }
  }
}

Post Rendering

Post Renderer Script
#!/bin/bash
# kustomize-post-renderer.sh

# Read manifests from stdin
cat > base.yaml

# Create kustomization.yaml
cat > kustomization.yaml <# Apply kustomize and output result
kustomize build .

# Usage:
# helm install myrelease ./mychart --post-renderer ./kustomize-post-renderer.sh

Helm Plugins

Popular Helm Plugins
# Install helm-diff plugin
helm plugin install https://github.com/databus23/helm-diff

# Use helm diff
helm diff upgrade myrelease ./mychart

# Install helm-secrets plugin
helm plugin install https://github.com/jkroepke/helm-secrets

# Use helm secrets
helm secrets enc secrets.yaml
helm secrets install myrelease ./mychart -f secrets.yaml

# List installed plugins
helm plugin list

# Update plugin
helm plugin update diff

# Remove plugin
helm plugin uninstall diff

Advanced Templating Patterns

Named Templates with Parameters

Advanced Template Patterns
{{- define "mychart.container" -}}
{{- $containerName := .containerName -}}
{{- $containerImage := .containerImage -}}
{{- $containerPort := .containerPort | default 8080 -}}
- name: {{ $containerName }}
  image: {{ $containerImage }}
  ports:
    - containerPort: {{ $containerPort }}
{{- end -}}

# Usage:
containers:
  {{- include "mychart.container" (dict "containerName" "app" "containerImage" .Values.image "containerPort" 3000) | nindent 2 }}
  {{- include "mychart.container" (dict "containerName" "sidecar" "containerImage" .Values.sidecarImage) | nindent 2 }}

Complex Logic with Variables

Complex Template Logic
{{- $replicas := .Values.replicaCount -}}
{{- if .Values.autoscaling.enabled -}}
  {{- $replicas = .Values.autoscaling.minReplicas -}}
{{- end -}}

{{- $serviceName := printf "%s-svc" (include "mychart.fullname" .) -}}
{{- $servicePort := .Values.service.port | int -}}

{{- range $i := until (int $replicas) }}
---
apiVersion: v1
kind: Service
metadata:
  name: {{ $serviceName }}-{{ $i }}
spec:
  selector:
    statefulset.kubernetes.io/pod-name: {{ include "mychart.fullname" $ }}-{{ $i }}
  ports:
    - port: {{ $servicePort }}
{{- end }}

Best Practices

📋 Chart Development Best Practices

  • Use Semantic Versioning: Follow SemVer for chart versions (MAJOR.MINOR.PATCH)
  • Document Everything: Include comprehensive README.md and NOTES.txt
  • Default Values: Provide sensible defaults in values.yaml
  • Schema Validation: Use values.schema.json for input validation
  • Label Consistency: Use standard Kubernetes labels
  • Resource Limits: Always set resource requests and limits
  • Security Context: Run containers as non-root by default

🏗️ Template Best Practices

  • Use _helpers.tpl: Define reusable template snippets
  • Avoid Hardcoding: Make everything configurable via values
  • Quote Strings: Always quote string values in templates
  • Whitespace Control: Use {{- and -}} to control whitespace
  • Fail Fast: Use 'required' function for mandatory values
  • Comments: Document complex template logic

🔒 Security Best Practices

  • Image Tags: Never use 'latest' tag in production
  • Secrets Management: Don't store secrets in values.yaml
  • RBAC: Include proper RBAC resources
  • Network Policies: Include network policies when needed
  • Pod Security: Set appropriate security contexts
  • Image Pull Secrets: Support private registries

📦 Release Management

  • Atomic Upgrades: Use --atomic flag for production upgrades
  • Dry Run: Always test with --dry-run first
  • Rollback Plan: Document rollback procedures
  • Release Notes: Maintain detailed release notes
  • Testing: Include helm test hooks
  • Backup: Backup releases before major upgrades

Common Patterns

Multi-Environment Configuration

Environment-specific Values
# values-dev.yaml
environment: development
replicaCount: 1
resources:
  limits:
    memory: 256Mi
    cpu: 100m

# values-prod.yaml
environment: production
replicaCount: 3
resources:
  limits:
    memory: 1Gi
    cpu: 500m

# Install with environment-specific values
helm install myapp ./mychart -f values-prod.yaml

GitOps-Friendly Structure

GitOps Directory Structure
deployments/
├── base/
│   ├── Chart.yaml
│   ├── values.yaml
│   └── templates/
├── environments/
│   ├── dev/
│   │   ├── values.yaml
│   │   └── secrets.yaml (encrypted)
│   ├── staging/
│   │   ├── values.yaml
│   │   └── secrets.yaml (encrypted)
│   └── prod/
│       ├── values.yaml
│       └── secrets.yaml (encrypted)
└── scripts/
    ├── deploy.sh
    └── rollback.sh

🏋️ Interactive Exercises

Exercise 1: Helm Concepts Quiz

What is the difference between a Chart and a Release?
A Chart is a running instance, Release is the package
A Chart is the package definition, Release is a running instance
They are the same thing
Chart is for development, Release is for production

Exercise 2: Template Functions

Which function would you use to ensure a value is provided?
{{ .Values.database | default "mysql" }}
{{ required "Database is required!" .Values.database }}
{{ .Values.database | quote }}
{{ if .Values.database }}{{ .Values.database }}{{ end }}

Exercise 3: Create Your First Chart

Click the buttons above to walk through creating a Helm chart...

Exercise 4: Hook Ordering

You have two pre-install hooks with weights -5 and 10. Which executes first?
The hook with weight 10
The hook with weight -5
They execute simultaneously
The order is random

Exercise 5: Debug a Chart

Fix the issues in this Chart.yaml:

Broken Chart.yaml
Issues Found
Click "Check Chart" to find issues...

📚 Additional Resources

🎉 Congratulations!

You've completed the comprehensive Helm Charts guide. You now have the knowledge to:

  • Create and structure Helm charts
  • Write complex templates with functions and logic
  • Manage dependencies and hooks
  • Set up and work with chart repositories
  • Follow best practices for production deployments

Keep practicing and exploring advanced features to become a Helm expert!