Kubernetes Helm Charts: Package and Deploy Applications
Master Helm for Kubernetes deployments. Learn chart creation, templates, values, dependencies, and best practices for production applications.
Moshiour Rahman
Advertisement
What is Helm?
Helm is the package manager for Kubernetes. It simplifies deploying and managing applications by packaging Kubernetes resources into reusable charts.
Why Helm?
| Without Helm | With Helm |
|---|---|
| Many YAML files | Single chart |
| Manual updates | Versioned releases |
| Copy/paste configs | Parameterized templates |
| Hard to rollback | Easy rollbacks |
Installation
# macOS
brew install helm
# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Verify
helm version
Helm Basics
Add Repository
# Add official stable repo
helm repo add bitnami https://charts.bitnami.com/bitnami
# Update repos
helm repo update
# Search charts
helm search repo nginx
helm search hub wordpress
Install Charts
# Install nginx
helm install my-nginx bitnami/nginx
# Install with custom values
helm install my-nginx bitnami/nginx \
--set replicaCount=3 \
--set service.type=LoadBalancer
# Install from values file
helm install my-nginx bitnami/nginx -f values.yaml
# Install in namespace
helm install my-nginx bitnami/nginx -n production --create-namespace
Manage Releases
# List releases
helm list
helm list --all-namespaces
# Get release info
helm status my-nginx
helm get values my-nginx
helm get manifest my-nginx
# Upgrade release
helm upgrade my-nginx bitnami/nginx --set replicaCount=5
# Rollback
helm rollback my-nginx 1
# Uninstall
helm uninstall my-nginx
Creating Charts
Chart Structure
my-chart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default values
├── charts/ # Dependencies
├── templates/ # Kubernetes manifests
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── _helpers.tpl # Template helpers
│ └── NOTES.txt # Post-install notes
└── .helmignore
Chart.yaml
apiVersion: v2
name: my-app
description: A Helm chart for my application
type: application
version: 1.0.0
appVersion: "2.0.0"
keywords:
- app
- web
maintainers:
- name: John Doe
email: john@example.com
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
values.yaml
# Default values
replicaCount: 2
image:
repository: myapp
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: false
className: nginx
hosts:
- host: myapp.local
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
postgresql:
enabled: true
auth:
database: myapp
username: myuser
env:
- name: NODE_ENV
value: production
Templates
Deployment Template
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
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:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
{{- if .Values.env }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
Service Template
# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "my-app.selectorLabels" . | nindent 4 }}
Ingress Template
# templates/ingress.yaml
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.className }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
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 }}
Helper Templates
# templates/_helpers.tpl
{{/*
Expand the name of the chart.
*/}}
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- 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 }}
{{/*
Common labels
*/}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
ConfigMap Template
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "my-app.fullname" . }}-config
labels:
{{- include "my-app.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.config }}
{{ $key }}: {{ $value | quote }}
{{- end }}
Template Functions
Common Functions
# String functions
{{ .Values.name | upper }}
{{ .Values.name | lower }}
{{ .Values.name | title }}
{{ .Values.name | quote }}
{{ .Values.name | trunc 63 }}
{{ .Values.name | trimSuffix "-" }}
# Default values
{{ .Values.name | default "myapp" }}
# Conditionals
{{- if .Values.enabled }}
enabled: true
{{- end }}
{{- if and .Values.a .Values.b }}
both: true
{{- end }}
{{- if or .Values.a .Values.b }}
either: true
{{- end }}
# Loops
{{- range .Values.items }}
- {{ . }}
{{- end }}
{{- range $key, $value := .Values.map }}
{{ $key }}: {{ $value }}
{{- end }}
# Include template
{{- include "my-app.labels" . | nindent 4 }}
# toYaml
{{- toYaml .Values.resources | nindent 12 }}
Dependencies
Add Dependencies
# Chart.yaml
dependencies:
- name: postgresql
version: "12.1.2"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: "17.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# Download dependencies
helm dependency update
# Build dependencies
helm dependency build
Use Dependencies
# values.yaml
postgresql:
enabled: true
auth:
database: mydb
username: myuser
password: mypassword
redis:
enabled: true
architecture: standalone
Testing Charts
Lint and Template
# Lint chart
helm lint ./my-chart
# Render templates locally
helm template my-release ./my-chart
# Render with custom values
helm template my-release ./my-chart -f production.yaml
# Dry run install
helm install my-release ./my-chart --dry-run --debug
Unit Tests
# Install helm-unittest plugin
helm plugin install https://github.com/helm-unittest/helm-unittest
# Run tests
helm unittest ./my-chart
# tests/deployment_test.yaml
suite: deployment tests
templates:
- deployment.yaml
tests:
- it: should have correct replicas
set:
replicaCount: 3
asserts:
- equal:
path: spec.replicas
value: 3
- it: should use correct image
set:
image.repository: myapp
image.tag: v1.0.0
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: myapp:v1.0.0
Packaging and Publishing
# Package chart
helm package ./my-chart
# Create index for repo
helm repo index . --url https://example.com/charts
# Push to OCI registry
helm push my-chart-1.0.0.tgz oci://registry.example.com/charts
Summary
| Command | Purpose |
|---|---|
helm install | Deploy chart |
helm upgrade | Update release |
helm rollback | Revert release |
helm template | Render templates |
helm lint | Validate chart |
helm package | Create package |
Helm simplifies Kubernetes deployments with reusable, versioned, and parameterized charts.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
Kubernetes for Beginners: Complete Guide to Container Orchestration
Learn Kubernetes from scratch. Understand pods, deployments, services, and how to deploy your first application to a Kubernetes cluster with practical examples.
DevOpsService Mesh with Istio: Complete Kubernetes Guide
Master Istio service mesh for Kubernetes. Learn traffic management, security, observability, and build resilient microservices architectures.
DevOpsGitHub Actions CI/CD: Automate Your Development Workflow
Master GitHub Actions for CI/CD pipelines. Learn workflows, jobs, actions, secrets management, and deploy to any cloud platform automatically.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.