Once you run more than a couple of services on Kubernetes, hand-editing YAML stops scaling. Helm packages those files into versioned, configurable charts you can install, upgrade and roll back with one command.
Helm is the package manager of Kubernetes. Instead of writing a dozen YAML files for an app, you install one chart with a single command and keep your settings in a values file. This guide covers installing Helm, using existing charts, writing your own chart, secrets, dependencies and GitOps. The commands work on k3s, which runs well on a single self-managed VPS, and on any other Kubernetes cluster.
Why Helm
Without Helm, a typical app needs ~8-15 YAML files (Deployment, Service, Ingress, ConfigMap, Secret, PVC, HPA, ServiceAccount, RBAC...). Values duplicated across files. Updating 5 apps = editing 75 files.
Helm:
- Templated YAML — DRY
- Versioned releases — deploy v1.2, rollback to v1.1 instantly
- Dependencies — "Chart X needs a database" declared once in
Chart.yaml - Repositories — thousands of pre-built charts listed on Artifact Hub
Install Helm
Use the install script or package from the official Helm documentation (helm.sh, "Installing Helm"). Read a script before piping it to a shell. Then check:
helm versionHelm runs on any machine that can reach your cluster with a kubeconfig. On a k3s server, point Helm at the k3s config:
export KUBECONFIG=/etc/rancher/k3s/k3s.yamlThe everyday commands in this guide (install, upgrade, rollback, template, lint) are the same on current Helm releases. Check the release notes when you move to a new major version.
Pattern 1 — Install existing charts
Add a repository:
helm repo add podinfo https://stefanprodan.github.io/podinfo
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo updatepodinfo is a small demo web app that is handy for learning Helm. Many charts are also published to OCI registries, and you can install those directly with an oci:// address instead of adding a repository.
Search:
helm search repo podinfo
# NAME CHART VERSION APP VERSION DESCRIPTION
# podinfo/podinfo 6.x.x 6.x.x Podinfo Helm chart for KubernetesInstall:
helm install demo podinfo/podinfo \
--namespace demo \
--create-namespace \
--set replicaCount=2Check:
helm list --all-namespaces
kubectl get pods -n demoUninstall:
helm uninstall demo -n demoThat's it: an app running in Kubernetes with a handful of commands.
Pattern 2 — Customise via values.yaml
Charts accept configuration. Instead of --set flags, use a values file:
helm show values podinfo/podinfo > my-values.yamlEdit my-values.yaml:
replicaCount: 2
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 256MiKeys differ from chart to chart; always start from the chart's own helm show values output.
Install with values:
helm install demo podinfo/podinfo -f my-values.yaml -n demo --create-namespaceKeep my-values.yaml in git (without secrets) for reproducible deployments.
Pattern 3 — Upgrade in place
# Change something in my-values.yaml
helm upgrade demo podinfo/podinfo -f my-values.yaml -n demoHelm computes the diff, applies only what changed, keeps revision history.
Rollback to previous version:
helm history demo -n demo
# REVISION UPDATED STATUS CHART
# 1 ... superseded podinfo-6.x.x
# 2 ... superseded podinfo-6.x.x
# 3 ... deployed podinfo-6.x.x
helm rollback demo 2 -n demoPattern 4 — Write your own chart
helm create myappCreates:
myapp/
├── Chart.yaml # chart metadata
├── values.yaml # default config
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── serviceaccount.yaml
│ ├── _helpers.tpl # reusable template functions
│ └── NOTES.txt # shown after install
└── charts/ # sub-charts (dependencies)Chart.yaml:
apiVersion: v2
name: myapp
description: My Node.js API
type: application
version: 0.1.0 # chart version
appVersion: "1.0.0" # app versionvalues.yaml:
replicaCount: 2
image:
repository: ghcr.io/yourorg/myapp
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: traefik # k3s ships Traefik as its ingress controller
host: myapp.yourcompany.com
tls: true # adjust templates/ingress.yaml to match these keys
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
env:
NODE_ENV: "production"
LOG_LEVEL: "info"
# Secrets (DATABASE_URL, JWT_SECRET) go in a Kubernetes Secret, not here. See Pattern 5.templates/deployment.yaml:
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 }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 3000
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 10 }}Template language is Go templates with Sprig functions. Common patterns:
# Conditional
{{- if .Values.ingress.enabled }}
...
{{- end }}
# Iterate over a list of {name, value} items
{{- range .Values.extraEnv }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
# Include helper
{{- include "myapp.fullname" . }}
# Default values
image: {{ .Values.image.repository | default "busybox" }}Test your chart
# Render templates locally without touching the cluster
helm template myapp ./myapp
# Dry-run against the cluster
helm install myapp ./myapp --dry-run
# Diff (requires helm-diff plugin)
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade myapp ./myapp
# Lint
helm lint ./myappPattern 5 — Secrets management
Never commit secrets in values.yaml.
Option A — External secrets:
Use External Secrets Operator with a secrets store such as HashiCorp Vault or OpenBao, or a cloud secrets manager. Your chart references the Kubernetes Secret by name; the values live outside git.
Option B — Sealed Secrets:
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# sealed-secret.yaml is safe to commit; only the controller in your cluster can decrypt itOption C — Manually via --set:
kubectl create secret generic myapp-secrets -n production \
--from-literal=JWT_SECRET="$JWT_SECRET"Then reference myapp-secrets from the Deployment with envFrom or secretKeyRef. Avoid --set for secrets: the values are stored in the Helm release record and are visible to anyone who can run helm get values.
Pattern 6 — Dependencies
Chart.yaml:
dependencies:
- name: postgresql
version: "x.y.z" # pin an exact version
repository: "oci://registry.example.com/charts"
condition: postgresql.enabled
- name: valkey
version: "x.y.z"
repository: "https://charts.example.com"
condition: valkey.enabledDownload deps:
helm dependency updatePick dependency charts that are actively maintained and whose container images you can pull. Some popular public chart catalogues changed their free-image terms in 2025, so check a chart's current status on Artifact Hub before you depend on it. For production PostgreSQL on Kubernetes, many teams use an operator such as CloudNativePG instead of a plain chart.
Install with deps:
helm install myapp ./myapp \
--set postgresql.enabled=true \
--set valkey.enabled=trueOne chart, whole stack (app + DB + cache).
Pattern 7 — Publish your chart
helm package ./myapp
# Creates myapp-0.1.0.tgz
# Option 1: push to an OCI registry (GHCR, Docker Hub, Harbor)
helm push myapp-0.1.0.tgz oci://ghcr.io/yourorg/charts
# Option 2: a static chart repository (for example GitHub Pages)
helm repo index ./charts-repoOthers can:
helm install myapp oci://ghcr.io/yourorg/charts/myapp --version 0.1.0
# or, for a static repository:
helm repo add yourorg https://yourorg.github.io/charts
helm install myapp yourorg/myappPattern 8 — GitOps with Helm
Argo CD or Flux continuously sync your cluster with Helm charts stored in git.
# Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/myapp
path: chart
targetRevision: main
helm:
valueFiles: [values-prod.yaml]
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: truePush to git → Argo CD detects → applies chart. Always-in-sync cluster.
Common pitfalls
* in production breaks on surprise upgrades. Pin exact versions.values.yaml committed to githelm lint and helm template before every release.helm dependency update after changing Chart.yaml.--create-namespace.helm upgrade without --installhelm upgrade --install handles both cases.FAQ
Helm or Kustomize?
Helm — templating, versioning, dependencies, repositories. Kustomize — overlays, no templating, in-tree with kubectl. Helm wins for 3rd-party apps; Kustomize for your own simple overlays.
Do I need Helm for small k3s deployments?
For one or two services, plain manifests are fine. Helm pays off once you have several services with shared configuration, or when you install third-party software that ships as a chart.
Can I use Helm without a Kubernetes cluster?
Yes — helm template renders YAML files you can kubectl-apply manually. But you lose release tracking.
Chart registry options?
Artifact Hub for discovery; OCI registries (GHCR, Docker Hub, Harbor) for storage; or a static repository on GitHub Pages or ChartMuseum.
Breaking changes across Helm versions?
Helm 2, with its in-cluster Tiller component, is long dead; ignore any tutorial that installs Tiller. Current Helm is client-only. Read the release notes before moving to a new major version, and test your charts with it first.
Can I run k3s and Helm on Domain India hosting?
On a Domain India VPS, yes: it is self-managed with full root access on KVM, so you can install k3s and Helm yourself. Shared hosting and the App Platform can't run Kubernetes.
Running this on Domain India
- VPS: self-managed, full root access, KVM virtualisation. You can install k3s and Helm on it; you are responsible for installing, securing and updating the cluster.
- Backups: VPS plans include no backups or snapshots. Keep your charts and values in git, and back up persistent volumes and the k3s datastore yourself.
- Shared hosting and App Platform: they can't run Kubernetes. For a single web app without a cluster, the App Platform deploys from GitHub or with a deploy token.
Ready to run your own cluster? Choose a Domain India VPS, set up CI with the GitHub Actions deploy guide, or open a ticket with hosting questions.
A self-managed KVM VPS with full root access, ready for k3s, Helm and your own charts.
View VPS plans