⚓ Kubernetes Helm Charts

Master Helm 3 - The Package Manager for Kubernetes. Learn to create, deploy, and manage applications using Helm charts for streamlined Kubernetes deployments.

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

Install Helm
# 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

Custom Values File
# 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
Installing with Custom Values
# 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)

Priority 1

Chart's values.yaml

Default values defined in the chart

Priority 2

Parent chart's values

Values from parent chart override subchart defaults

Priority 3

User-supplied files (-f)

Custom values files provided during install/upgrade

Priority 4

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 Chart Scaffold
# 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
Chart.yaml
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
values.yaml
# 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
values.schema.json
{
  "$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

deployment.yaml Template
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

_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 }}

{{/*
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

Common 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 }}
Advanced Templating Examples
# 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

Pre-Install Hook Example
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

templates/tests/test-connection.yaml
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 Library Chart
# 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

Working with OCI Registries
# 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

Kustomize Post-Renderer
#!/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:

  1. Create a parent chart with three subcharts
  2. Frontend: React app served by nginx
  3. Backend: Node.js API with environment-specific configs
  4. Database: PostgreSQL with persistent storage
  5. Configure inter-service communication
  6. 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:

  1. Create base values.yaml
  2. Create environment-specific values files
  3. Deploy to dev, staging, and production
  4. Implement different resource limits per environment
  5. 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:

  1. Structure a Git repository for Helm charts
  2. Create umbrella chart for multiple microservices
  3. Implement semantic versioning
  4. Set up automated testing with helm test
  5. Configure ArgoCD applications
  6. Implement automatic sync and rollback