Why this matters
As an MLOps Engineer, you package and deploy model services, batch jobs, and data pipelines repeatedly across dev, staging, and prod. Helm lets you template Kubernetes manifests, version configurations, and roll out updates safely. Real tasks you will handle:
- Deploying a model inference service with different image tags and resources per environment.
- Rolling out updates and quickly rolling back if a release misbehaves.
- Sharing reusable charts for training jobs, batch predictions, and feature pipelines.
Who this is for
- Beginners to Helm who already know basic Kubernetes objects (Deployment, Service, ConfigMap, Secret).
- MLOps engineers standardizing model deployments across environments.
- Data scientists transitioning to productionizing ML workloads.
Prerequisites
- Basic Kubernetes knowledge: Pods, Deployments, Services, Namespaces.
- kubectl installed and access to a test cluster (e.g., local kind or Minikube).
- Docker image of a simple ML service or a public image you can reference.
Concept explained simply
Helm is a package manager for Kubernetes. A Helm chart is a folder of templates and default values. When you run Helm, it renders those templates with your values and installs a release into the cluster. You can upgrade, rollback, and uninstall releases.
Mental model: Chart = function, Values = arguments, Rendered Manifests = output, Release = running instance of that output in your cluster.
Core parts of a chart
- Chart.yaml: chart metadata (name, version, description).
- values.yaml: default configurable values (image, resources, replicas).
- templates/: Kubernetes YAML files with Go templates (e.g., Deployment, Service).
- helpers templates (optional): common name labels and annotations.
- Release lifecycle: helm install → helm upgrade → helm rollback/uninstall.
- Repositories: where charts are stored and pulled from (you can also use local charts).
Worked examples
Example 1: Minimal inference service chart
Create a simple chart that deploys a single-replica model service with a ClusterIP Service.
# Create chart
helm create ml-infer
# Clean up extras (optional)
rm -rf ml-infer/templates/tests
# Edit Chart.yaml (key fields)
apiVersion: v2
name: ml-infer
version: 0.1.0
description: Minimal ML inference service
# values.yaml (essentials)
image:
repository: myrepo/ml-infer
tag: "0.1.0"
pullPolicy: IfNotPresent
replicaCount: 1
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
# templates/deployment.yaml (snippet)
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "ml-infer.fullname" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "ml-infer.name" . }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "ml-infer.name" . }}
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
resources: {{- toYaml .Values.resources | nindent 12 }}
ports:
- containerPort: 8080
# templates/service.yaml (snippet)
apiVersion: v1
kind: Service
metadata:
name: {{ include "ml-infer.fullname" . }}
spec:
type: {{ .Values.service.type }}
selector:
app.kubernetes.io/name: {{ include "ml-infer.name" . }}
ports:
- port: {{ .Values.service.port }}
targetPort: 8080
# Dry-run render
helm template demo ./ml-infer
# Install
helm install demo ./ml-infer
# Upgrade later with a new image tag
helm upgrade demo ./ml-infer --set image.tag=0.1.1
Result: a Deployment and Service for your inference container. Upgrades change image tags consistently.
Example 2: Environment-specific values (dev vs prod)
Keep different configs per environment using separate values files.
# values-dev.yaml
replicaCount: 1
image:
tag: "0.2.0-dev"
resources:
requests:
cpu: "50m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "256Mi"
# values-prod.yaml
replicaCount: 3
image:
tag: "0.2.0"
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
# Install dev
helm install demo-dev ./ml-infer -f values-dev.yaml
# Install prod
helm install demo-prod ./ml-infer -f values-prod.yaml
Result: the same chart deploys differently by environment, without editing templates.
Example 3: Configurable model version via ConfigMap and rollout
Expose model settings from values into a ConfigMap mounted into the container.
# values.yaml (add)
model:
name: fraud-detector
version: "2024-01-15"
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "ml-infer.fullname" . }}-config
data:
MODEL_NAME: {{ .Values.model.name | quote }}
MODEL_VERSION: {{ .Values.model.version | quote }}
# templates/deployment.yaml (mount as env)
env:
- name: MODEL_NAME
valueFrom:
configMapKeyRef:
name: {{ include "ml-infer.fullname" . }}-config
key: MODEL_NAME
- name: MODEL_VERSION
valueFrom:
configMapKeyRef:
name: {{ include "ml-infer.fullname" . }}-config
key: MODEL_VERSION
# Upgrade to roll out a new model version
helm upgrade demo ./ml-infer --set model.version=2024-02-01
Result: a clean rollout when the model version changes, with values tracked in release history.
Common mistakes and self-check
- Hardcoding values: If you see image tags or resource limits hardcoded in templates, move them to values.yaml. Self-check: run
helm templateand confirm outputs reflect overrides from--setor-f. - Forgetting resource requests/limits: ML pods without requests can be evicted or starve. Self-check: values.yaml must include requests and limits, and they render into Deployment.
- Putting secrets in plain values.yaml: Avoid storing sensitive data in plain text. Prefer Kubernetes Secrets created securely and referenced by the chart. Self-check: search for passwords/tokens in repo; keep them out of version control.
- Upgrading without dry-run: Self-check with
helm upgrade --dry-run --debugto catch template errors early. - Inconsistent labels/selectors: Make sure Service selectors match Deployment labels. Self-check: cross-verify labels in rendered manifests.
Exercises
Do these to build confidence. You can validate output using helm template and, if you have a cluster, by installing into a disposable namespace.
Exercise 1: Pack a batch prediction CronJob
Goal: Create a Helm chart that schedules a batch prediction job (CronJob) with a configurable image and schedule.
- Create a chart named
batch-predict. - Add
values.yamlfields:image.repository,image.tag,schedule(e.g., "0 * * * *"), andresources. - Template a
CronJobthat runspython predict.pywith ENVMODEL_VERSIONfrom values. - Provide two values files: dev (every 15 min) and prod (hourly).
- Dry-run with dev and prod files; confirm schedule and tags differ.
Exercise 2: Optional GPU toggle for inference
Goal: Extend the ml-infer chart to optionally request 1 NVIDIA GPU in prod.
- Add
gpu.enabled(bool) andgpu.count(int) to values. - In the Deployment template, conditionally include
resources.limits["nvidia.com/gpu"]when enabled. - Create
values-prod.yamlthat enables GPU and setsgpu.count: 1. - Dry-run for dev (no GPU) and prod (GPU present). Verify differences.
Checklist: Ready to ship?
- Values are not hardcoded in templates.
helm templaterenders valid Kubernetes YAML without errors.- Service selectors match Deployment labels.
- Resource requests/limits present and environment-specific files work.
- Upgrade path tested with
--dry-run.
Practical projects
- Project A: Unified chart for training + inference — One chart with sub-templates to deploy either a training Job or an inference Deployment based on a value switch. Deliverables: values-dev/prod, README with commands, successful dry-run renders.
- Project B: Blue/Green model rollout — Extend the inference chart to deploy two Deployments (blue/green) and a Service selector controlled by values. Deliverables: values-blue.yaml and values-green.yaml demonstrating a traffic switch via label change.
- Project C: Scheduled feature computation — A CronJob chart that mounts a ConfigMap for SQL and writes to a PVC. Deliverables: chart + values showing storage class, schedule, and resource tuning.
Learning path
- Before this: Kubernetes basics (Pods, Deployments, Services).
- This step: Helm basics (charts, values, releases, overrides).
- Next: Advanced templating (helpers, conditionals, loops), dependencies, and chart testing.
- Later: GitOps with continuous delivery, progressive rollouts, and observability for ML services.
Next steps
- Refactor common labels/annotations into a
_helpers.tpltemplate. - Add a NOTES.txt to guide operators after install (port-forward, health checks).
- Introduce a
values-staging.yamland practice promoting images via values only. - Set up a simple CI step that runs
helm lintandhelm templateon pull requests.
Mini challenge
Turn the minimal inference chart into a reusable library by parameterizing container port, readiness/liveness probes, and optional autoscaling (HPA). Provide dev/prod values showing different probe thresholds and min/max replicas. Validate with helm template.
Quick Test
Take the quick test below to check your understanding. Available to everyone; only logged-in users get saved progress.