What is Helm?
Helm is the package manager for Kubernetes, simplifying the deployment and management of applications. Think of it as the "apt" or "yum" for Kubernetes.
Charts
Packages of pre-configured Kubernetes resources
- Templates for Kubernetes manifests
- Default configuration values
- Documentation and metadata
- Dependencies management
Repositories
Storage locations for Helm charts
- Public charts (Artifact Hub)
- Private repositories
- OCI registries support
- Local chart development
Releases
Instances of charts running in Kubernetes
- Unique release names
- Version tracking
- Upgrade and rollback
- Release history
Find Chart
Search repositories
Configure
Customize values
Install
Deploy to cluster
Manage
Upgrade/Rollback
Helm 3 vs Helm 2
- No Tiller: Helm 3 removed the server-side component (Tiller)
- Improved Security: Uses Kubernetes RBAC directly
- JSON Schema: Chart values validation
- OCI Support: Store charts in container registries
- Library Charts: Reusable chart components
Getting Started with Helm
Installation
# macOS
brew install helm
# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Windows (Chocolatey)
choco install kubernetes-helm
# Verify installation
helm version
Basic Commands
Repository Management
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm repo list
helm search repo nginx
Chart Installation
helm install myapp bitnami/nginx
helm install myapp ./mychart
helm install myapp chart.tgz
helm install --dry-run myapp ./chart
Release Management
helm list
helm status myapp
helm get values myapp
helm get manifest myapp
Upgrades & Rollbacks
helm upgrade myapp bitnami/nginx
helm rollback myapp 1
helm history myapp
helm uninstall myapp
Working with Values
# values.yaml
replicaCount: 3
image:
repository: nginx
pullPolicy: IfNotPresent
tag: "1.21.0"
service:
type: LoadBalancer
port: 80
ingress:
enabled: true
className: nginx
hosts:
- host: example.com
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# Using values file
helm install myapp bitnami/nginx -f values.yaml
# Using --set flag
helm install myapp bitnami/nginx \
--set service.type=NodePort \
--set replicaCount=5
# Multiple values files (later files override)
helm install myapp ./mychart \
-f values.yaml \
-f values-prod.yaml \
--set image.tag=v2.0.0
# From URL
helm install myapp bitnami/nginx \
-f https://example.com/values.yaml
Values Precedence (Lowest to Highest)
Chart's values.yaml
Default values defined in the chart
Parent chart's values
Values from parent chart override subchart defaults
User-supplied files (-f)
Custom values files provided during install/upgrade
Command line (--set)
Individual values set via command line (highest priority)
Creating Helm Charts
Chart Structure
- mychart/ ← Chart directory
- Chart.yaml ← Chart metadata
- values.yaml ← Default configuration
- values.schema.json ← JSON schema for values
- charts/ ← Chart dependencies
- templates/ ← Template files
- deployment.yaml
- service.yaml
- ingress.yaml
- hpa.yaml
- NOTES.txt ← Post-install notes
- _helpers.tpl ← Template helpers
- crds/ ← Custom Resource Definitions
- .helmignore ← Patterns to ignore
- README.md ← Chart documentation
Creating Your First Chart
# Create new chart
helm create mychart
# Chart structure
tree mychart/
# Validate chart
helm lint mychart/
# Package chart
helm package mychart/
# Install from local directory
helm install myrelease ./mychart
apiVersion: v2
name: mychart
description: A Helm chart for my application
type: application # or 'library' for reusable charts
version: 0.1.0 # Chart version
appVersion: "1.0.0" # Version of the app being deployed
keywords:
- webapp
- nodejs
- microservice
home: https://github.com/myorg/mychart
sources:
- https://github.com/myorg/myapp
maintainers:
- name: John Doe
email: john@example.com
url: https://example.com
dependencies:
- name: postgresql
version: "11.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
tags:
- database
- name: redis
version: "17.x.x"
repository: https://charts.bitnami.com/bitnami
alias: cache
condition: redis.enabled
annotations:
category: Backend
licenses: Apache-2.0
# Default values for mychart
replicaCount: 1
image:
repository: myapp
pullPolicy: IfNotPresent
tag: "" # Defaults to chart appVersion
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
service:
type: ClusterIP
port: 80
targetPort: http
annotations: {}
ingress:
enabled: false
className: "nginx"
annotations: {}
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
# Database configuration
postgresql:
enabled: true
auth:
database: myapp
username: myuser
password: changeme
# Cache configuration
redis:
enabled: false
auth:
enabled: true
password: changeme
{
"$schema": "https://json-schema.org/draft-07/schema#",
"properties": {
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 10,
"description": "Number of replicas"
},
"image": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"pattern": "^[a-z0-9-_/]+$"
},
"tag": {
"type": "string",
"pattern": "^[a-z0-9.-]+$"
},
"pullPolicy": {
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"]
}
},
"required": ["repository", "pullPolicy"]
},
"service": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer"]
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
}
},
"required": ["replicaCount", "image", "service"]
}
Chart Development Best Practices
- Use semantic versioning for chart versions
- Keep charts simple and focused on a single application
- Provide comprehensive values.yaml with sensible defaults
- Add values.schema.json for validation
- Include detailed README with examples
- Use helm lint and helm test for validation
- Version control your charts
Template Language & Functions
Template Basics
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:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "mychart.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- if .Values.postgresql.enabled }}
- name: DATABASE_HOST
value: {{ include "mychart.fullname" . }}-postgresql
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "mychart.fullname" . }}-postgresql
key: password
{{- end }}
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
Helper Templates
{{/*
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 }}
{{/*
Generate backend URL
*/}}
{{- define "mychart.backendUrl" -}}
{{- if .Values.ingress.enabled }}
{{- $host := first .Values.ingress.hosts }}
{{- if .Values.ingress.tls }}
https://{{ $host.host }}
{{- else }}
http://{{ $host.host }}
{{- end }}
{{- else }}
http://{{ include "mychart.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}
{{- end }}
{{- end }}
Template Functions
# String Functions
{{ .Values.name | upper }} # Convert to uppercase
{{ .Values.name | lower }} # Convert to lowercase
{{ .Values.name | title }} # Title case
{{ .Values.name | quote }} # Add quotes
{{ .Values.text | trunc 63 }} # Truncate to 63 chars
{{ .Values.text | trimSuffix "-" }} # Remove suffix
{{ .Values.text | trimPrefix "pre" }} # Remove prefix
{{ .Values.text | replace "old" "new" }} # Replace text
# Default Values
{{ .Values.port | default 8080 }}
{{ .Values.name | default (printf "%s-app" .Release.Name) }}
# Conditionals
{{- if .Values.enabled }}
# content here
{{- else if .Values.alternate }}
# alternate content
{{- else }}
# default content
{{- end }}
# Loops
{{- range .Values.ports }}
- port: {{ . }}
{{- end }}
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val }}
{{- end }}
# Include and Template
{{ include "mychart.labels" . }}
{{ template "mychart.fullname" . }}
# Indentation
{{ include "mychart.labels" . | indent 4 }}
{{ include "mychart.labels" . | nindent 4 }}
# Type Conversion
{{ int .Values.count }}
{{ float64 .Values.percentage }}
{{ toString .Values.number }}
# List Functions
{{ .Values.list | first }}
{{ .Values.list | last }}
{{ .Values.list | uniq }}
{{ .Values.list | has "item" }}
# Dictionary Functions
{{ .Values.dict | keys }}
{{ .Values.dict | values }}
{{ .Values.dict | hasKey "key" }}
{{ merge .Values.dict1 .Values.dict2 }}
# Date Functions
{{ now | date "2006-01-02" }}
{{ dateInZone "2006-01-02" (now) "UTC" }}
# Crypto Functions
{{ .Values.password | sha256sum }}
{{ .Values.data | b64enc }}
{{ .Values.encoded | b64dec }}
# YAML/JSON
{{ .Values.config | toYaml | nindent 2 }}
{{ .Values.config | toJson }}
{{ .Values.jsonString | fromJson }}
# Using with and range together
{{- with .Values.ingress }}
{{- if .enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "mychart.fullname" $ }}
annotations:
{{- range $key, $value := .annotations }}
{{ $key }}: {{ $value | quote }}
{{- end }}
spec:
{{- range .hosts }}
- host: {{ .host }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "mychart.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
# Complex conditionals
{{- if or .Values.dev.enabled (eq .Values.environment "development") }}
# Development configuration
{{- else if and (not .Values.production.strict) (has .Values.stage (list "qa" "staging")) }}
# QA/Staging configuration
{{- else }}
# Production configuration
{{- end }}
# Using lookup function (Helm 3.1+)
{{- $secret := lookup "v1" "Secret" .Release.Namespace "existing-secret" }}
{{- if $secret }}
- name: EXISTING_PASSWORD
valueFrom:
secretKeyRef:
name: existing-secret
key: password
{{- end }}
# Fail with required
{{ required "A valid .Values.database.host is required!" .Values.database.host }}
Template Gotchas
- Use
{{-
and-}}
to control whitespace - Remember that
nil
is different from empty string - Use
$
to reference root context in loops - Test templates with
helm template
before installing - Be careful with
toYaml
indentation
Advanced Helm Features
Hooks
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-db-init
labels:
{{- include "mychart.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "1"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
metadata:
name: {{ include "mychart.fullname" . }}-db-init
spec:
restartPolicy: Never
containers:
- name: db-init
image: postgres:13
command:
- sh
- -c
- |
PGPASSWORD=$POSTGRES_PASSWORD psql -h $DATABASE_HOST -U $DATABASE_USER -d $DATABASE_NAME <<-EOSQL
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_username ON users(username);
EOSQL
env:
- name: DATABASE_HOST
value: {{ .Values.database.host }}
- name: DATABASE_USER
value: {{ .Values.database.user }}
- name: DATABASE_NAME
value: {{ .Values.database.name }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "mychart.fullname" . }}-db
key: password
Tests
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test-connection"
labels:
{{- include "mychart.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args:
- '{{ include "mychart.fullname" . }}:{{ .Values.service.port }}'
- '--timeout=5'
- '--tries=3'
restartPolicy: Never
---
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test-api"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test-api
image: curlimages/curl:latest
command:
- sh
- -c
- |
echo "Testing API endpoints..."
curl -f http://{{ include "mychart.fullname" . }}:{{ .Values.service.port }}/health || exit 1
curl -f http://{{ include "mychart.fullname" . }}:{{ .Values.service.port }}/ready || exit 1
echo "All tests passed!"
restartPolicy: Never
Library Charts
# common/Chart.yaml
apiVersion: v2
name: common
description: Common library chart
type: library
version: 1.0.0
---
# common/templates/_deployment.yaml
{{- define "common.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "common.fullname" . }}
labels:
{{- include "common.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "common.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "common.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
{{- range .Values.ports }}
- name: {{ .name }}
containerPort: {{ .containerPort }}
protocol: {{ .protocol | default "TCP" }}
{{- end }}
{{- if .Values.env }}
env:
{{- toYaml .Values.env | nindent 8 }}
{{- end }}
{{- if .Values.resources }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
{{- end }}
{{- end }}
---
# Using the library chart
# mychart/Chart.yaml
dependencies:
- name: common
version: "1.0.0"
repository: "file://../common"
# mychart/templates/deployment.yaml
{{- include "common.deployment" . }}
OCI Registry Support
# Login to OCI registry
helm registry login registry.example.com -u username
# Push chart to OCI registry
helm package mychart/
helm push mychart-0.1.0.tgz oci://registry.example.com/helm-charts
# Pull chart from OCI registry
helm pull oci://registry.example.com/helm-charts/mychart --version 0.1.0
# Install from OCI registry
helm install myrelease oci://registry.example.com/helm-charts/mychart
# Using AWS ECR
aws ecr get-login-password --region us-west-2 | \
helm registry login --username AWS --password-stdin \
123456789.dkr.ecr.us-west-2.amazonaws.com
helm push mychart-0.1.0.tgz \
oci://123456789.dkr.ecr.us-west-2.amazonaws.com/
Post Rendering
#!/bin/bash
# post-renderer.sh
cat <&0 > all.yaml
kustomize build . && rm all.yaml
---
# kustomization.yaml
resources:
- all.yaml
patchesStrategicMerge:
- patches/deployment.yaml
commonLabels:
environment: production
---
# Install with post-renderer
chmod +x post-renderer.sh
helm install myrelease ./mychart --post-renderer ./post-renderer.sh
Chart Repositories
Public Repositories
- Artifact Hub: hub.helm.sh - Central registry
- Bitnami: High-quality, production-ready charts
- Stable (Deprecated): Legacy charts repository
Private Repository Options
- ChartMuseum - Open source Helm chart repository
- Harbor - Enterprise container registry with Helm support
- Nexus Repository - Universal repository manager
- GitLab Package Registry - Integrated with GitLab
- GitHub Pages - Free hosting for public charts
Hands-On Practice
Exercise 1: Create a Full-Stack Application Chart
Create a Helm chart for a complete web application with frontend, backend, and database.
Requirements:
- Create a parent chart with three subcharts
- Frontend: React app served by nginx
- Backend: Node.js API with environment-specific configs
- Database: PostgreSQL with persistent storage
- Configure inter-service communication
- Add Ingress for external access
# fullstack-app/Chart.yaml
apiVersion: v2
name: fullstack-app
description: Full-stack web application
type: application
version: 1.0.0
appVersion: "1.0.0"
dependencies:
- name: postgresql
version: "11.9.13"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
---
# fullstack-app/values.yaml
frontend:
enabled: true
replicaCount: 2
image:
repository: myapp/frontend
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
backend:
enabled: true
replicaCount: 3
image:
repository: myapp/backend
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 3000
env:
NODE_ENV: production
LOG_LEVEL: info
postgresql:
enabled: true
auth:
postgresPassword: postgres123
username: appuser
password: apppass123
database: myappdb
persistence:
enabled: true
size: 10Gi
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: myapp.example.com
paths:
- path: /api
pathType: Prefix
backend: backend
- path: /
pathType: Prefix
backend: frontend
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com
---
# fullstack-app/templates/frontend-deployment.yaml
{{- if .Values.frontend.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "fullstack-app.fullname" . }}-frontend
labels:
app: frontend
{{- include "fullstack-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.frontend.replicaCount }}
selector:
matchLabels:
app: frontend
{{- include "fullstack-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app: frontend
{{- include "fullstack-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: frontend
image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
ports:
- containerPort: 80
env:
- name: REACT_APP_API_URL
value: "http://{{ include "fullstack-app.fullname" . }}-backend:{{ .Values.backend.service.port }}"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
{{- end }}
---
# fullstack-app/templates/backend-deployment.yaml
{{- if .Values.backend.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "fullstack-app.fullname" . }}-backend
labels:
app: backend
{{- include "fullstack-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.backend.replicaCount }}
selector:
matchLabels:
app: backend
{{- include "fullstack-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app: backend
{{- include "fullstack-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: backend
image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
ports:
- containerPort: 3000
env:
{{- range $key, $value := .Values.backend.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- if .Values.postgresql.enabled }}
- name: DATABASE_HOST
value: {{ include "fullstack-app.fullname" . }}-postgresql
- name: DATABASE_USER
value: {{ .Values.postgresql.auth.username }}
- name: DATABASE_NAME
value: {{ .Values.postgresql.auth.database }}
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "fullstack-app.fullname" . }}-postgresql
key: password
{{- end }}
{{- end }}
Exercise 2: Multi-Environment Deployment
Deploy the same application to different environments with environment-specific configurations.
Tasks:
- Create base values.yaml
- Create environment-specific values files
- Deploy to dev, staging, and production
- Implement different resource limits per environment
- Use different ingress hosts
# values-dev.yaml
environment: development
replicaCount: 1
image:
tag: develop
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
ingress:
enabled: true
hosts:
- host: dev.myapp.example.com
paths:
- path: /
pathType: Prefix
---
# values-staging.yaml
environment: staging
replicaCount: 2
image:
tag: staging
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
ingress:
enabled: true
hosts:
- host: staging.myapp.example.com
paths:
- path: /
pathType: Prefix
---
# values-prod.yaml
environment: production
replicaCount: 5
image:
tag: v1.0.0
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 5
maxReplicas: 20
targetCPUUtilizationPercentage: 70
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com
---
# Deploy commands
helm install myapp-dev ./mychart -f values.yaml -f values-dev.yaml -n dev
helm install myapp-staging ./mychart -f values.yaml -f values-staging.yaml -n staging
helm install myapp-prod ./mychart -f values.yaml -f values-prod.yaml -n production
Challenge: GitOps with Helm
Implement a GitOps workflow using Helm charts and ArgoCD.
Requirements:
- Structure a Git repository for Helm charts
- Create umbrella chart for multiple microservices
- Implement semantic versioning
- Set up automated testing with helm test
- Configure ArgoCD applications
- Implement automatic sync and rollback