Intro
Helm is the de facto package manager for Kubernetes, but its power to deploy complex applications quickly also creates security risk if not handled carefully. A chart that works in a development cluster may introduce secrets, overly broad RBAC permissions, or untrusted repositories in production. This guide walks through concrete steps to harden Helm from a practitioner's perspective, focused on developers, DevOps consultants, and technical startup teams.
You will learn how to inventory your Helm environment, configure secure defaults, manage secrets safely, verify releases, and recover from common failures. Every recommendation includes a command or configuration snippet you can run, expected output, and a recovery path. The goal is operational safety: observe before changing, limit the blast radius, use placeholders instead of real secrets, verify results, and document recovery steps before you need them.
This guide assumes Helm 3.12 or later, Kubernetes 1.27 or later, and a working kubectl context. Some examples reference GitLab CI/CD or Docker when they affect prerequisites or observability, but the focus remains on Helm itself.
Version and Environment Inventory
Before changing anything, you need an accurate picture of your current Helm deployment. Knowing your exact version, topology, and configuration helps you choose compatible security options and avoids breaking an already working environment.
Check Helm Version and Kubernetes Context
Run these two commands with read-only access:
helm version --short
kubectl config current-context
Expected output looks like:
v3.12.0+g123abc
my-prod-cluster
If you see a Helm v2 version string, stop and upgrade to Helm v3 before continuing. Helm v2 used a server-side component called Tiller with broad cluster permissions, and it reached end-of-life in November 2020. Migrating to Helm v3 removes that entire attack surface.
Record the version and context in your operations doc, along with today's date. This makes troubleshooting easier and gives you a rollback point.
Inventory Existing Helm Releases
List releases across all namespaces:
helm ls --all-namespaces
Sample output:
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
nginx-ing ingress 2 2023-10-01 14:22:31.123456 +0000 UTC deployed ingress-nginx-4.7.1 1.8.1
my-app default 5 2023-10-05 09:10:11.987654 +0000 UTC deployed my-app-1.2.0 2.0.0
Note the chart name, app version, and namespace. This list tells you which workloads might be affected by a change. If you do not have permission to list releases in all namespaces, fix your Kubernetes RBAC before proceeding; no Helm hardening step should require cluster-admin rights.
Identify Who Can Access Helm
Helm delegates authorization to Kubernetes RBAC. Check your own permissions:
kubectl auth can-i list secrets --namespace default
Expected output: yes or no. The secrets resource is where Helm stores release metadata by default. If the answer is no, you cannot manage releases in that namespace. Map out roles for each developer or service account before restricting anything.
Smallest Justified Change
Once you have the inventory, choose one scoped improvement. For example, if you find an old Helm v2 installation, upgrading to v3 is the only change. If all releases use default settings, you might start by enabling --atomic on installs in your CI pipeline. Write down the expected result before running anything.
Safe Configuration Path
Helm's default settings favor convenience over security. This section shows how to configure Helm and your charts to reduce risk without disrupting normal workflows.
Secure the Helm Client Configuration
Helm stores its repository cache and settings in your home directory. The main config file is ~/.config/helm/helm.yaml. Set restrictive permissions on that directory:
chmod 700 ~/.config/helm
chmod 600 ~/.config/helm/helm.yaml
On Windows, use the equivalent ACL controls for %USERPROFILE%\AppData\Roaming\helm.
Add Repositories Explicitly and Pin Versions
Avoid installing charts from unknown repositories. Add only trusted repos and verify their content:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami/nginx --versions | head -n 5
The search output shows available versions. Pin your install to a specific chart version instead of using latest:
helm install my-nginx bitnami/nginx --version 15.1.1 --namespace web
Pinning prevents a silently updated chart from introducing a vulnerable dependency. If you must use a local chart, verify its provenance with helm verify after signing it.
Set Values with a Values File, Not Command-Line Flags
Command-line --set flags are easy to typo and expose values in shell history. Use a values file checked into version control:
# values-prod.yaml
replicaCount: 3
service:
type: ClusterIP
resources:
limits:
cpu: "1"
memory: 512Mi
Install with the file:
helm install my-nginx bitnami/nginx --version 15.1.1 --values values-prod.yaml --namespace web
This approach keeps configuration reviewable and auditable. Do not commit real secrets in this file; use placeholders or a secret management solution described later.
Use Atomic Installs and Upgrades
The --atomic flag rolls back a release automatically if the operation fails. This prevents half-deployed states in production:
helm upgrade --install my-nginx bitnami/nginx --version 15.1.1 --values values-prod.yaml --namespace web --atomic --timeout 5m
Expected result: either the release is deployed successfully, or Helm rolls back to the previous revision and prints an error. If the command times out, increase --timeout or check pod status with kubectl get pods -n web.
Secrets Management
Helm stores release metadata in Kubernetes Secrets by default. Those secrets are base64-encoded, not encrypted, so anyone with read access to the namespace can decode them. For application secrets, charts often require passwords, tokens, or API keys. Handling these securely is critical.
What Helm Stores in Secrets
When you run helm install, Helm creates a Secret like sh.helm.release.v1.my-nginx.v1 in the release namespace. It contains the chart manifest, values, and hooks. Inspect one (without exposing sensitive data publicly):
kubectl get secret sh.helm.release.v1.my-nginx.v1 -n web -o jsonpath='{.data.release}' | base64 -d | gzip -d | head -c 200
This shows the stored data. Because it is only base64-encoded, you should encrypt it at rest. Enable encryption for Kubernetes Secrets using a KMS provider, or use a tool like Sealed Secrets or Vault to manage the values before they reach Helm.
External Secret Management Patterns
Do not put application secrets directly in values.yaml or in Helm release secrets. Instead, use one of these patterns:
- Kubernetes External Secrets: Store secrets in AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault, and sync them into Kubernetes Secrets. The chart references the Secret name, not the value.
- Helm Secrets plugin: Encrypt the values file with SOPS or age before committing it to git.
For example, with the Helm Secrets plugin using SOPS and age:
# Create an encrypted values file
helm secrets encrypt secrets.yaml > secrets.yaml.enc
# Decrypt and install
helm secrets install my-app ./chart -f secrets.yaml.enc -n prod
The plugin decrypts in memory and passes values to Helm without writing plaintext to disk. Verify the encryption worked by checking the file starts with ENC[AES256_GCM,data:.
Use Kubernetes Secrets for Credentials
If you must pass credentials to a chart, create a Kubernetes Secret first, then reference it in the chart values:
kubectl create secret generic app-creds --from-literal=password='ChangeMe123!' --namespace prod
In your values file:
existingSecret: app-creds
The chart must support existingSecret. Many official charts do. This keeps the password out of Helm release history and your shell history. Rotate the password regularly; for example, every 90 days.
Verification and Diagnostics
After making a change, verify it did what you intended and did not break anything else. Helm provides built-in commands for this, and you can augment them with Kubernetes checks.
Dry-Run Before Applying
Every install or upgrade should start with a dry-run. This renders the templates and shows you exactly what would be created or changed:
helm upgrade --install my-nginx bitnami/nginx --version 15.1.1 --values values-prod.yaml --namespace web --dry-run
The output includes every Kubernetes resource and the values used. Look for unexpected permissions, hardcoded secrets, or resource limits. If you see a secret with a real password in plaintext, stop and fix the chart configuration.
Lint the Chart
If you are developing a chart, run helm lint before packaging:
helm lint ./my-chart
Expected output includes 1 chart(s) linted, 0 chart(s) failed when clean. Warnings about missing required fields, bad YAML, or insecure defaults should be addressed. Add a lint step to your CI pipeline in GitLab or GitHub Actions.
Test the Release
Use helm test to run chart tests in a live cluster:
helm test my-nginx -n web
If the chart defines tests, you will see pod logs and a success/failure status. For charts without tests, manually verify key resources:
kubectl get pods -n web
kubectl get secrets -n web
Ensure only expected pods are running and no extra secrets exist.
Check RBAC Permissions Created by the Chart
After installation, review the RBAC objects:
kubectl get roles,rolebindings,clusterroles,clusterrolebindings -n web
Look for ClusterRoles or ClusterRoleBindings, which grant cluster-wide access. Remove any that are not necessary. For example, if a chart creates a ClusterRole just to list pods in one namespace, replace it with a Role.
Failure Modes and Recovery
Even careful hardening can go wrong. This section describes common failures, their symptoms, and how to recover without losing data or availability.
Upgrade Fails and Leaves Release in Failed State
Symptom: helm upgrade errors out, and helm ls shows the release with STATUS: failed.
Recovery: Roll back to the last known good revision.
helm rollback my-nginx 3 -n web
Check available revisions with helm history my-nginx -n web. If the rollback also fails, you may need to delete the failed release and reinstall from your values file. Before deleting, export the current state:
helm get manifest my-nginx -n web > my-nginx-manifest.yaml
This preserves the YAML for inspection.
Secret Not Found for Release
Symptom: helm ls works, but helm status or helm rollback returns Error: release: not found or similar.
Cause: The underlying Kubernetes Secret was accidentally deleted, or its name was changed.
Recovery: Recreate the release from your source of truth. Install the chart again with the same values:
helm install my-nginx bitnami/nginx --version 15.1.1 --values values-prod.yaml --namespace web
This creates a new release revision. To prevent this, restrict delete permissions on Secrets in production namespaces to only a small set of operators.
RBAC Denial After Tightening Permissions
Symptom: A developer or CI pipeline suddenly gets Error: query: failed to query with labels: secrets is forbidden when running helm ls.
Cause: You restricted Secret read permissions but forgot that Helm needs to read release secrets.
Recovery: Grant the necessary minimal permissions. For a developer working in namespace dev:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: helm-user
namespace: dev
rules:
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Bind it to the user or service account. Avoid cluster-wide roles for Helm users. Revisit these permissions quarterly to ensure they match current needs.
Chart Pulls a Malicious Image
Symptom: Pods crash or behave suspiciously after an upgrade; image scan reports vulnerabilities.
Recovery: Immediately roll back to the previous revision with helm rollback. Then inspect the chart source. Use image scanning in your CI pipeline (e.g., Trivy, Snyk) and pin image tags by digest in your values file:
image:
repository: nginx
tag: 1.25.3@sha256:abcdef123456...
Common Pitfalls and How to Avoid Them
Several recurring mistakes undermine Helm security. Recognizing them saves time and prevents security incidents.
1. Using Helm v2 with Tiller
Why it happens: Legacy clusters were never migrated, or scripts still call helm init. How to avoid: Upgrade to Helm v3, which removed Tiller and uses direct Kubernetes API calls. Check for any helm2 binaries in your CI runners and replace them.
2. Committing Real Secrets to Git
Why it happens: Developers use values.yaml as a convenience and accidentally commit passwords or tokens. How to avoid: Use placeholders in values files and a secret management tool like Helm Secrets, Sealed Secrets, or External Secrets. Add pre-commit hooks to scan for secret patterns (e.g., gitleaks). If a secret is committed, rotate it immediately and scrub git history.
3. Overly Broad RBAC for Helm Service Accounts
Why it happens: Quick fixes grant cluster-admin to a CI service account so that Helm can deploy anywhere. How to avoid: Grant the least privilege needed. For CI, create a dedicated namespace with a Role and RoleBinding, not a ClusterRole. Use kubectl auth can-i --list to review permissions. Revisit monthly.
4. Ignoring Chart Provenance and Signing
Why it happens: Teams add repositories without checking authenticity, trusting that stable or bitnami are safe by default. How to avoid: Verify chart signatures with helm verify after configuring pgp keys, or use provenance files. Prefer repositories with a good security track record and pin chart versions.
5. Release Bloat and Unused Secrets
Why it happens: Multiple upgrades leave many old revision Secrets, increasing the attack surface if an old secret contains sensitive data. How to avoid: Limit release history with --history-max 10 on installs/upgrades. Periodically list releases and clean up with helm uninstall for defunct apps. Example:
helm upgrade my-nginx bitnami/nginx --values values-prod.yaml --namespace web --history-max 10
Operations Checklist
Use this checklist before and after any Helm security change. Assign a single accountable owner for each item and review the checklist monthly or after any significant change.
| Item | Owner | Frequency | Command / Verification | Expected Result |
|---|---|---|---|---|
| Confirm Helm version is v3+ | DevOps lead (e.g., Priya Shah) | Quarterly | helm version --short | v3.12.0 or later |
| Confirm kubectl context is correct | Operator on duty | Before every change | kubectl config current-context | Production cluster name |
| List all releases and namespaces | DevOps lead | Monthly | helm ls --all-namespaces | No unexpected releases |
| Check RBAC for Helm users | Kubernetes admin (e.g., Mark Chen) | Monthly | kubectl auth can-i --list -n <namespace> | No cluster-admin for regular users |
| Verify secrets are encrypted at rest | Security engineer | Quarterly | kubectl get secrets -o yaml and check KMS config | Secrets are not plaintext |
| Scan chart images for vulnerabilities | Security engineer | Weekly in CI | trivy image <image> | No critical or high findings |
| Review and rotate application secrets | App owner (e.g., Sarah Lopez) | 90 days | Follow secret management tool procedure | All secrets rotated successfully |
| Test rollback procedure | DevOps lead | Monthly | helm rollback <release> <revision> --dry-run | Command succeeds without errors |
| Review chart provenance | DevOps lead | When adding new repos | helm verify <chart> | Signature valid |
| Update Helm and plugins | DevOps lead | Monthly | helm version, check plugin releases | Latest stable version installed |
Conclusion
Helm security hardening is not a one-time fix but an ongoing set of practices: know your environment, configure safely, manage secrets with encryption, verify every change, and prepare for failures. Each recommendation in this guide is version-scoped, observable, and reversible where possible.
Start with one low-risk verification: run helm ls --all-namespaces, record the output, check your Helm version, and review who has access to the Helm release secrets. Then choose the next smallest justified change, such as pinning chart versions or enabling --atomic in your CI pipeline.
A reliable hardening workflow makes failure visible, protects sensitive values, limits changes to the intended resource, and defines recovery verification before an incident forces the decision. Revisit your Helm security posture quarterly, and adjust as your Kubernetes estate grows.