Helm
Helm is a templating engine plus a release ledger. Separating those two jobs in your head explains every Helm problem you will have.
What is it?#
Helm packages a set of Kubernetes manifests into a chart — templated YAML plus a file of default values — and installs it as a named release it keeps track of.
Why it exists#
Without it you hit three problems, in this order:
| Problem | The manual workaround | Why it fails |
|---|---|---|
| The same app in dev/staging/prod | Copy the YAML three times | They drift; a fix lands in one |
| Installing someone else's software | Read their docs, copy 15 manifests | No upgrade path, no uninstall |
| Undoing a bad deploy | kubectl apply the old files — if you kept them | You did not keep them |
What it is made of#
| Piece | What it is |
|---|---|
| Chart | The package: templates + defaults + metadata |
| Values | The inputs. Layered: chart defaults → -f file → --set |
| Release | One installation of a chart, with a name and a revision number |
| Repository | Where charts are hosted (an HTTP index, or an OCI registry) |
| Revision | A numbered snapshot of a release, stored as a Secret in-cluster |
How to use it#
Installing something that already exists#
# 1. Add a repository and refresh the index
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# 2. Look before you install — what can this chart be configured with?
helm show values bitnami/redis | less
# 3. Install as a NAMED release. The name is how you refer to it forever.
helm install my-redis bitnami/redis \
--namespace data --create-namespace \
--set auth.password=... \
--version 20.1.0 # PIN the chart version. Always.
# 4. What is installed on this cluster?
helm list -AThe three commands that prevent most mistakes#
# Render locally — no cluster contact. Catches template errors instantly.
helm template my-redis bitnami/redis -f prod-values.yaml
# Dry run against the cluster — also runs server-side validation
helm install my-redis bitnami/redis --dry-run --debug
# See exactly what an upgrade WOULD change (needs the helm-diff plugin)
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade my-redis bitnami/redis -f prod-values.yamlWriting a chart#
apiVersion: apps/v1
kind: Deployment
metadata:
# include a named template from _helpers.tpl; nindent keeps YAML valid
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
# default lets the chart work with no values supplied
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
- containerPort: {{ .Values.service.port }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}replicaCount: 2
image:
repository: nginx
tag: "" # empty → falls back to Chart.appVersion
service:
port: 80
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 256Mi }Upgrading and rolling back#
# Upgrade, or install if it does not exist yet — the CI-friendly form
helm upgrade --install my-redis bitnami/redis \
-f prod-values.yaml \
--version 20.1.0 \
--atomic \ # roll back automatically if it fails
--wait --timeout 5m # wait for resources to be Ready
# History and rollback
helm history my-redis
helm rollback my-redis 3 # back to revision 3--atomic --wait is the difference between a deploy and a hopeWithout --wait, Helm returns success as soon as the objects are accepted by the API server — exactly the kubectl apply problem, where a crash-looping pod still reports green.
--wait blocks until resources are Ready. --atomic adds automatic rollback on failure, so a broken upgrade returns you to the last working revision instead of leaving the release half-applied.
In CI, always use both.
What goes wrong#
| Symptom | Cause | Fix |
|---|---|---|
cannot re-use a name that is still in use | A release with that name exists | helm list -A; use upgrade --install |
has no deployed releases | A previous install failed and left a failed release | helm uninstall then reinstall, or --force |
did not find expected key | Indentation from indent vs nindent | Render with helm template and read the output |
| Upgrade hangs forever | --wait with pods that never become Ready | kubectl describe a new pod; check the probes |
field is immutable | Changing a Deployment selector, or a PVC size downward | Delete and recreate, or use a new resource name |
| Values seem ignored | Wrong key path, or precedence | helm get values <release> shows what was actually used |
| Rollback does not restore data | Helm manages manifests, not PVC contents | Restore the data separately; Helm never touches volumes |
Secrets visible in helm get values | Values are stored in a release Secret | Keep secrets out of values; use a secret manager |
helm rollback does not roll back your dataThis surprises people during an incident, which is the worst time to learn it.
Helm's ledger records manifests. Rolling back changes the Deployment image, the ConfigMap contents, the replica count — everything declarative.
It does not undo:
- a database migration your app ran on startup
- anything written to a PersistentVolume
- messages consumed from a queue
- rows changed by the new version
So a rollback of an app that ran a destructive migration restores the old code against a new schema, which is frequently worse than the bug you were fixing. This is the same constraint as rolling updates: migrations must be backwards-compatible, because rollback assumes the data layer did not move.
| العَرَض | السبب | الحل |
|---|---|---|
cannot re-use a name | فيه release بنفس الاسم | helm list -A واستخدم upgrade --install |
has no deployed releases | تنصيبة قديمة فشلت وسابت release بحالة failed | helm uninstall وبعدين نصّب |
did not find expected key | مشكلة مسافات (indent بدل nindent) | helm template واقرا الناتج |
| الـ upgrade بيقعد معلّق | --wait وبودات مش بتبقى Ready | kubectl describe لبود جديد، شوف الـ probes |
field is immutable | بتغيّر selector في Deployment | امسح واعمل من الأول، أو غيّر الاسم |
| القيم مش بتتطبّق | مسار مفتاح غلط، أو أولوية | helm get values <release> بيوريك اللي اتطبّق فعلاً |
helm rollback مش بيرجّع الداتادي بتفاجئ الناس وقت الأزمة، وده أوحش وقت تتعلمها فيه.
Helm بيسجّل الـ manifests. فالرجوع بيغيّر صورة الـ Deployment، ومحتوى الـ ConfigMap، وعدد النسخ — كل الحاجات الوصفية.
وهو مش بيرجّع:
- migration التطبيق شغّلها وهو بيقوم
- أي حاجة اتكتبت على PersistentVolume
- رسايل اتاخدت من طابور
- صفوف الإصدار الجديد غيّرها في الداتابيز
والنتيجة الوحشة: لو رجّعت تطبيق شغّل migration مدمّرة، إنت رجّعت الكود القديم على schema جديدة — وده غالباً أوحش من الباج اللي كنت بتصلّحه.
ونفس القيد بتاع الـ rolling updates: الـ migrations لازم تكون متوافقة للخلف، عشان الرجوع بيفترض إن طبقة الداتا ما اتحركتش.
Helm versus Kustomize#
Both solve "the same manifests, different per environment", differently.
| Helm | Kustomize | |
|---|---|---|
| Mechanism | Template, then render | Patch a base with overlays |
| Input | values.yaml | kustomization.yaml patches |
| Valid YAML before rendering | No — it is Go templates | Yes — always real YAML |
| Release tracking | Yes, with rollback | No — it just produces YAML |
| Third-party software | The ecosystem standard | Rare |
| Complexity ceiling | Nested conditionals get unreadable fast | Deep overlay chains get hard to trace |
Built into kubectl | No | Yes — kubectl apply -k |
A reasonable default: Kustomize for your own applications, Helm for installing other people's. They combine — Kustomize can post-process rendered Helm output, and Argo CD supports that directly.
الاتنين بيحلّوا نفس المشكلة — «نفس الـ manifests بإعدادات مختلفة لكل بيئة» — بطريقتين مختلفتين.
| Helm | Kustomize | |
|---|---|---|
| الطريقة | قالب بيترندر | ترقيع لأساس بطبقات |
| المدخلات | values.yaml | patches |
| YAML صالح قبل الرندر؟ | لأ، ده Go templates | أيوه، دايماً YAML حقيقي |
| بيتابع الإصدارات؟ | أيوه، ومعاه rollback | لأ، بيطلّع YAML وخلاص |
| برامج الناس التانية | هو المعيار | نادر |
جوه kubectl؟ | لأ | أيوه: kubectl apply -k |
والاختيار المعقول:
- Kustomize لتطبيقاتك إنت.
- Helm عشان تنصّب برامج الناس (Prometheus، Redis، Ingress controllers).
وينفع تستخدم الاتنين مع بعض: Kustomize يقدر يعالج ناتج Helm بعد الرندر، و Argo CD بيدعم ده مباشرة.
لو قريت شرح قديم، هتلاقي كلام عن حاجة اسمها Tiller — سيرفر كان بيتنصّب جوه الكلاستر ومعاه صلاحيات واسعة، وكان كابوس أمني.
اتشال خلاص في Helm 3. دلوقتي helm عميل بس، بيكلّم الـ API server بصلاحياتك إنت من الـ kubeconfig.
فلو لقيت شرح بيتكلم عن helm init أو Tiller، ده شرح قديم، سيبه.
Interview corner · الأسئلة اللي بتتسأل#
What does Helm give you that kubectl apply -f does not?
A release ledger. kubectl apply is stateless — it knows only the files in front of it right now.
Three consequences:
kubectl apply -f ./manifests | helm upgrade | |
|---|---|---|
| Delete a manifest from the directory | Object stays in the cluster forever, orphaned | Removed, because Helm diffs against the previous release |
| Undo a bad deploy | Re-apply old files — if you still have them | helm rollback <release> <revision> |
| "What is installed here?" | Inspect the cluster and guess | helm list -A |
Plus templating, which Kustomize also solves — so the ledger is the part that is genuinely Helm's.
kubectl apply --prune exists and partially closes the first gap, but it is label-selector based and easy to get dangerously wrong. Naming it shows you know the landscape rather than reciting Helm marketing.
سجل الإصدارات (release ledger). الـ kubectl apply بلا ذاكرة — هو عارف الملفات اللي قصاده دلوقتي وبس.
kubectl apply -f | helm upgrade | |
|---|---|---|
| تمسح ملف من الفولدر | الأوبجكت بيفضل في الكلاستر للأبد | بيتشال، عشان Helm بيقارن |
| ترجّع ديبلوي باظ | تطبّق الملفات القديمة — لو لسه معاك | helm rollback |
| «إيه المنصّب هنا؟» | تبص في الكلاستر وتخمّن | helm list -A |
وطبعاً القوالب — بس Kustomize بيحلها كمان، فالسجل ده هو الحاجة اللي Helm لوحده بيقدمها.
والإضافة اللي تبيّن إنك فاهم المجال: إن kubectl apply --prune موجودة وبتحل جزء من المشكلة الأولى، بس هي مبنية على labels وسهل جداً تغلط فيها بشكل خطير. إنك تسمّيها بيوري إنك عارف المنظر كله مش بتردّد دعاية Helm.
A helm upgrade fails halfway. What state is the cluster in, and what do you do?
Partially applied, unless you used --atomic.
Helm applies resources in a dependency-aware order. If it fails at resource 12 of 20, the first 11 are live and the release is marked failed.
helm history my-app # find the last DEPLOYED revision
helm status my-app # what state is it in now
kubectl describe pod <new> # why did it actually fail
helm rollback my-app <n> # back to a known-good revisionThe trap: if the first ever install failed, there is no previous revision, and helm upgrade then fails with has no deployed releases. You must helm uninstall and install again.
The prevention, and the real answer:
helm upgrade --install my-app ./chart --atomic --wait --timeout 5m--wait makes Helm block until resources are actually Ready rather than merely accepted, and --atomic rolls back automatically on failure — so the cluster is never left half-upgraded.
نصّ منشورة، إلا لو استخدمت --atomic.
Helm بيطبّق الموارد بترتيب معيّن. فلو فشل عند المورد ١٢ من ٢٠، أول ١١ موجودين شغالين، والـ release متعلّم عليها failed.
helm history my-app # آخر revision كانت DEPLOYED
kubectl describe pod <جديد> # فشل ليه أصلاً
helm rollback my-app <n>والفخ: لو أول تنصيبة خالص هي اللي فشلت، مفيش revision قديمة ترجعلها، و helm upgrade بيقولك has no deployed releases. لازم helm uninstall وتنصّب من الأول.
والوقاية — وهي الإجابة الحقيقية:
helm upgrade --install my-app ./chart --atomic --wait --timeout 5mالـ --wait بتخلي Helm يستنى الموارد تبقى Ready فعلاً مش بس مقبولة، والـ --atomic بترجّع لوحدها لو فشل — فالكلاستر عمره ما يفضل نص منشور.
Where does Helm store release state, and why does that matter?
In Secrets in the release's namespace, one per revision, named sh.helm.release.v1.<release>.v<n> and containing a gzipped, base64-encoded copy of the rendered manifests and the values used.
kubectl get secret -n <ns> -l owner=helm
helm get values my-app # the values that were actually applied
helm get manifest my-app # the YAML that was actually appliedThree consequences that matter operationally:
- State lives in the cluster, not on your laptop. Any machine with credentials sees the same history — which is what makes Helm usable from CI.
- Lose the namespace, lose the history. Deleting a namespace destroys the release records with it;
helm rollbackis then impossible. - Anyone who can read Secrets in that namespace can read your values — including any password you passed with
--set. This is the argument for keeping secrets out of values entirely and using a secret manager or External Secrets, with RBAC on the namespace.
في Secrets جوه نفس الـ namespace، واحد لكل revision، اسمه sh.helm.release.v1.<release>.v<n>، وجواه نسخة مضغوطة من الـ manifests والقيم اللي اتنشرت بيها.
kubectl get secret -n <ns> -l owner=helm
helm get values my-app # القيم اللي اتطبّقت فعلاً
helm get manifest my-app # الـ YAML اللي اتطبّق فعلاًوتلات نتايج مهمة عملياً:
- الحالة في الكلاستر مش على جهازك. أي جهاز معاه صلاحيات بيشوف نفس التاريخ — وعشان كده Helm بينفع من الـ CI.
- تمسح الـ namespace، يضيع التاريخ. ومعاه تضيع إمكانية الـ rollback نهائياً.
- أي حد يقدر يقرا الـ Secrets في الـ namespace دي يقدر يقرا قيمك — وده بيشمل أي باسورد بعتّه بـ
--set.
وده بالظبط سبب إنك متحطش أسرار في الـ values خالص، وتستخدم secret manager أو External Secrets، ومعاهم RBAC على الـ namespace.
Key takeaways#
- Helm is a templating engine plus a release ledger. The ledger is the part
kubectl applycannot replace. helm templaterenders without a cluster — read third-party charts with it before you trust them, and debug your own with it.--atomic --waitin CI, always. Without them a failed upgrade leaves the release half-applied and reports success.- Pin the chart version with
--version, exactly as you pin image tags. - Release state is Secrets in the namespace — so values are readable by anyone with Secret access, and deleting the namespace destroys the history.
helm rollbackrestores manifests, never data. Migrations must still be backwards-compatible.- Kustomize for your apps, Helm for other people's is a defensible default.
- Helm = محرّك قوالب + سجل إصدارات. والسجل هو الجزء اللي
kubectl applyمش قادر يعوّضه. helm templateبيرندر من غير كلاستر — اقرا بيه charts الناس قبل ما تثق فيها، وظبّط بيه بتاعتك.--atomic --waitفي الـ CI، دايماً. من غيرهم الـ upgrade الفاشل بيسيب الـ release نص منشورة وبيقول نجح.- ثبّت نسخة الـ chart بـ
--version، بالظبط زي ما بتثبّت تاجات الصور. - حالة الإصدارات Secrets في الـ namespace — يعني القيم مقروءة لأي حد عنده صلاحية، ومسح الـ namespace بيضيّع التاريخ.
helm rollbackبيرجّع الـ manifests، مش الداتا أبداً. الـ migrations لازم تفضل متوافقة للخلف.- Kustomize لتطبيقاتك، و Helm لبرامج الناس — اختيار افتراضي محترم.
Last updated 2026-09-13