How to Package and Deploy Kubernetes Applications Using Helm Charts with Multi-environment Values Files
Learning how to package and deploy Kubernetes applications using Helm Charts with multi-environment values files is one of the most valuable skills a modern DevOps engineer can have. Helm is the official package manager for Kubernetes. It lets you define, install, and upgrade complex applications using reusable templates. Instead of managing dozens of raw YAML files per environment, you use a single chart with separate values files for development, staging, and production. This tutorial walks you through creating a Helm chart from scratch, structuring multi-environment values files, and deploying your application cleanly across environments. By the end, you’ll have a repeatable, professional deployment workflow you can use on any Kubernetes cluster.
Prerequisites and Requirements for Helm Chart Deployments
Before you start packaging and deploying Kubernetes applications with Helm, make sure you have the following in place.
Required tools:
- A working Kubernetes cluster (local with Minikube or a cloud provider like GKE, EKS, or AKS)
- kubectl installed and configured to reach your cluster
- Helm v3 installed on your local machine
- A basic understanding of Kubernetes objects (Deployments, Services, ConfigMaps)
- A terminal running Linux or macOS (Windows users should use WSL2)
You can install Helm by following the official instructions at the Helm installation documentation.
Assumed knowledge: You should be comfortable running kubectl commands and editing YAML files. You don’t need to be a Kubernetes expert.
Estimated time: 45–60 minutes.
How to Package Kubernetes Applications Using Helm Charts
Related tutorial: How to Master Linux System Log Management with Journalctl and Systemd-journald on Ubuntu Server
This section covers creating your Helm chart structure and writing reusable templates.
Step 1: Create a new Helm chart
Run the following command to scaffold a new chart called myapp:
helm create myapp
This generates a directory with this structure:
myapp/
Chart.yaml
values.yaml
charts/
templates/
deployment.yaml
service.yaml
_helpers.tpl
hpa.yaml
ingress.yaml
serviceaccount.yaml
NOTES.txt
You’ll work mostly inside templates/ and with values files.
Step 2: Edit Chart.yaml
Open myapp/Chart.yaml and update the metadata:
apiVersion: v2
name: myapp
description: A Helm chart for my application
type: application
version: 0.1.0
appVersion: "1.0.0"
The version field tracks your chart version. The appVersion tracks your application version. Keep them separate.
Step 3: Simplify the default deployment template
Open myapp/templates/deployment.yaml. Replace the default content with a clean, readable template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.service.port }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
Every value wrapped in {{ }} gets pulled from your values files at deploy time.
Step 4: Update the base values.yaml
The values.yaml file holds your default configuration. Edit it like this:
replicaCount: 1
image:
repository: nginx
tag: "1.25"
service:
type: ClusterIP
port: 80
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
This is your baseline. You’ll override specific values per environment in the next section.
Deploying Across Environments with Multi-environment Values Files
This is where the real power of Helm shows up. You create one values file per environment. Each file only overrides what’s different from the base values.yaml.
Step 5: Create environment-specific values files
Inside the myapp/ directory, create three new files:
touch myapp/values-dev.yaml
touch myapp/values-staging.yaml
touch myapp/values-prod.yaml
Step 6: Define values for each environment
Edit values-dev.yaml:
replicaCount: 1
image:
tag: "dev-latest"
resources:
limits:
cpu: 100m
memory: 128Mi
Edit values-staging.yaml:
replicaCount: 2
image:
tag: "staging-1.2.0"
resources:
limits:
cpu: 200m
memory: 256Mi
Edit values-prod.yaml:
replicaCount: 4
image:
tag: "1.2.0"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
Production gets more replicas and tighter resource definitions. Dev stays lightweight.
Step 7: Deploy to each environment
Helm merges your base values.yaml with the environment file at deploy time. Use the -f flag to pass the environment file:
# Deploy to development
helm install myapp-dev ./myapp -f ./myapp/values-dev.yaml -n development --create-namespace
# Deploy to staging
helm install myapp-staging ./myapp -f ./myapp/values-staging.yaml -n staging --create-namespace
# Deploy to production
helm install myapp-prod ./myapp -f ./myapp/values-prod.yaml -n production --create-namespace
Each environment gets its own Helm release name and Kubernetes namespace. This keeps them fully isolated.
Step 8: Upgrade a release
When you update your chart or values, use helm upgrade instead of helm install:
helm upgrade myapp-prod ./myapp -f ./myapp/values-prod.yaml -n production
You can also use the --install flag to handle both cases in one command:
helm upgrade --install myapp-prod ./myapp -f ./myapp/values-prod.yaml -n production
This is the pattern most CI/CD pipelines use. For deeper reading on Kubernetes deployment strategies, check the official Kubernetes Deployment documentation.
Troubleshooting Common Helm Chart Issues
Even experienced engineers hit snags. Here are the most common problems and how to fix them.
Error: rendered manifests contain a resource that already exists
This happens when you run helm install on an already-deployed release. Use helm upgrade --install instead. It handles both new installs and updates.
Error: YAML parse error on templates/deployment.yaml
Helm templates are sensitive to indentation. Use helm lint to catch errors before deploying:
helm lint ./myapp -f ./myapp/values-prod.yaml
Fix any indentation issues in your template files. The nindent function in templates controls spacing automatically.
Values not overriding correctly
Check that your environment values file uses the exact same key structure as values.yaml. A mismatched key name means Helm won’t override the default. Run this to see the final merged values:
helm get values myapp-prod -n production
Image pull errors in pods
If your pods show ImagePullBackOff, your image tag is likely wrong. Verify the tag exists in your container registry. Check it with:
kubectl describe pod -n production
Tip: Always run helm template to preview rendered YAML before deploying to any environment:
helm template myapp-prod ./myapp -f ./myapp/values-prod.yaml
This lets you catch template errors without touching your cluster.
Conclusion
You now know how to package and deploy Kubernetes applications using Helm Charts with multi-environment values files. You created a chart from scratch, built reusable templates, and structured separate values files for dev, staging, and production. Each
