Helm Best Practices

A comprehensive guide to Helm best practices covering chart structure, values management, versioning, security, and production guidelines for Helm charts.

Helm Best Practices Security
Why Helm Best Practices Matter

Following Helm best practices ensures your charts are maintainable, secure, reusable, and production-ready. Well-designed charts reduce operational overhead, prevent common pitfalls, and enable teams to collaborate effectively.

Key areas of focus include:

  • Chart Structure: Consistent organization for readability
  • Values Management: Clean configuration and overrides
  • Versioning: Semantic versioning for releases
  • Security: Secrets, RBAC, and signing
  • Testing: Linting, unit tests, integration tests
  • Documentation: README and comments
Key Principle: Treat Helm charts like software artifacts. Apply the same rigor to chart development as you would to application code.
Chart Structure Best Practices

Follow Standard Structure

Use the standard Helm directory structure: Chart.yaml, values.yaml, templates/, charts/, _helpers.tpl.

Name Charts Consistently

Use lowercase, hyphenated names that match the directory name. Avoid special characters.

Document in README

Include installation instructions, values documentation, and examples in README.md.

Use Helpers

Define reusable template functions in _helpers.tpl to avoid duplication.

Keep Templates Focused

One resource per template file. Use descriptive filenames (deployment.yaml, service.yaml).

Include Values Schema

Add values.schema.json for validation. Prevents misconfigurations before installation.
# Recommended chart structure my-app/ ├── Chart.yaml # Chart metadata ├── values.yaml # Default values ├── values.schema.json # Values validation ├── README.md # Documentation ├── .helmignore # Ignore patterns ├── charts/ # Dependencies ├── crds/ # CRDs ├── templates/ │ ├── _helpers.tpl # Template helpers │ ├── deployment.yaml # Deployment │ ├── service.yaml # Service │ ├── configmap.yaml # ConfigMap │ ├── secret.yaml # Secret │ ├── hpa.yaml # Autoscaler │ ├── ingress.yaml # Ingress │ ├── NOTES.txt # Post-install notes │ └── tests/ # Test resources │ └── test-connection.yaml └── ci/ # CI test values └── test-values.yaml # Chart.yaml - Complete metadata apiVersion: v2 name: my-app description: A Helm chart for my application type: application version: 1.2.3 appVersion: 2.5.0 home: https://example.com icon: https://example.com/icon.png maintainers: - name: John Doe email: john@example.com keywords: - web - microservice sources: - https://github.com/example/my-app dependencies: - name: postgresql version: 11.x.x repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled
Chart Structure Best Practices:
  • Use standard directory structure for consistency
  • Name charts with lowercase, hyphens
  • Document everything in README.md
  • Use helpers to avoid duplication
  • Include values.schema.json for validation
  • Add .helmignore to exclude files
  • Use NOTES.txt for post-install instructions
Values Management Best Practices

Organized Values

Group related values logically. Use comments to explain complex configurations.

Sensible Defaults

Provide production-ready defaults. Charts should work out of the box for common use cases.

Environment Values

Use separate values files for dev, staging, and production (values-dev.yaml, values-prod.yaml).

Never Store Secrets

Never store secrets in values.yaml. Use external secret management solutions.

Schema Validation

Use values.schema.json to validate values and provide better error messages.

Global Values

Use global values for shared configuration like registry, storage class, and image pull secrets.
# values.yaml - Well-organized with comments # Global settings global: imagePullSecrets: [] storageClass: standard registry: docker.io # Application settings replicaCount: 3 image: repository: nginx tag: latest pullPolicy: IfNotPresent # Service configuration service: type: ClusterIP port: 80 targetPort: 8080 # Resources resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" # Autoscaling autoscaling: enabled: false minReplicas: 1 maxReplicas: 10 targetCPUUtilizationPercentage: 80 # Environment-specific values # values-dev.yaml replicaCount: 1 image: tag: dev resources: requests: memory: "32Mi" cpu: "100m" # values-prod.yaml replicaCount: 5 image: tag: v1.2.3 resources: requests: memory: "256Mi" cpu: "500m" autoscaling: enabled: true minReplicas: 3 # Install with environment values helm install my-release ./my-chart -f values-prod.yaml # values.schema.json - Validation { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "replicaCount": { "type": "integer", "minimum": 1, "maximum": 10 }, "image": { "type": "object", "properties": { "repository": { "type": "string" }, "tag": { "type": "string" } }, "required": ["repository"] } }, "required": ["image"] }
Values Best Practices:
  • Organize values logically with clear sections
  • Provide production-ready defaults
  • Use environment-specific values files
  • Never store secrets in values.yaml
  • Use schema validation for complex charts
  • Document all values in README.md
  • Use global values for shared config
Versioning Best Practices

Semantic Versioning

Use SemVer (MAJOR.MINOR.PATCH) for chart versions. Increment appropriately.

Chart vs App Version

Keep chart version and appVersion separate. Chart version for chart changes, appVersion for app changes.

Maintain History

Use --history-max to control release history. Balance auditability with storage.

Tag in Git

Tag chart releases in Git for traceability. Use Git tags for version management.

Pin Dependencies

Pin dependency versions to avoid unexpected changes. Use exact versions or narrow ranges.

Changelog

Maintain a CHANGELOG.md for each chart documenting changes between versions.
# Chart.yaml versioning apiVersion: v2 name: my-app version: 1.2.3 # Chart version (SemVer) appVersion: 2.5.0 # Application version # Versioning rules: # MAJOR: Breaking changes # MINOR: New features (backward compatible) # PATCH: Bug fixes (backward compatible) # Dependency versioning dependencies: - name: postgresql version: "11.0.0" # Exact version - name: redis version: ">=16.0.0 <17.0.0" # Range - name: mongodb version: "~12.0.0" # Patch updates only # Release history management helm upgrade my-release ./my-chart --history-max 20 # Tag releases in Git git tag -a v1.2.3 -m "Release 1.2.3" git push origin v1.2.3 # CHANGELOG.md # Changelog ## [1.2.3] - 2025-01-15 ### Fixed - Fixed resource limits in deployment - Corrected service port configuration ## [1.2.2] - 2025-01-10 ### Added - Added HPA support - Added ingress configuration ## [1.2.1] - 2025-01-05 ### Changed - Updated default image tag to v1.2.1
Versioning Best Practices:
  • Follow semantic versioning strictly
  • Keep chart and app versions separate
  • Pin dependency versions
  • Tag releases in Git
  • Maintain a changelog
  • Limit release history appropriately
Security Best Practices

Secrets Management

Use Sealed Secrets, helm-secrets, or External Secrets Operator. Never store secrets in values.yaml.

RBAC

Use least privilege for Helm service accounts. Limit permissions to specific namespaces.

Chart Signing

Sign charts with PGP keys. Verify signatures before installation.

Vulnerability Scanning

Scan charts for vulnerabilities with Trivy, kube-score, or Snyk.

Security Contexts

Define security contexts in pods. Run as non-root, drop capabilities, read-only filesystem.

Policy Enforcement

Use OPA/Gatekeeper to enforce security policies on Helm deployments.
# Security context in deployment apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-app.fullname" . }} spec: template: spec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 seccompProfile: type: RuntimeDefault containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] add: ["NET_BIND_SERVICE"] # RBAC for Helm apiVersion: v1 kind: ServiceAccount metadata: name: helm-user namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: helm-user-role namespace: default rules: - apiGroups: [""] resources: ["pods", "services", "configmaps"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # Chart signing helm package ./my-chart --sign --key "Your Name" helm verify my-chart-1.0.0.tgz # Vulnerability scanning trivy config ./my-chart kube-score score ./rendered-manifests.yaml # Using Sealed Secrets apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: name: my-secret spec: encryptedData: password: AgBy... # Encrypted # Using External Secrets Operator apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: my-secret spec: secretStoreRef: name: vault-store kind: SecretStore target: name: my-secret data: - secretKey: password remoteRef: key: app/db property: password
Security Best Practices:
  • Never store secrets in values.yaml
  • Use external secret management
  • Apply least privilege RBAC
  • Sign and verify charts
  • Scan for vulnerabilities
  • Define security contexts
  • Enforce policies with OPA
Testing Best Practices

Lint Charts

Run helm lint before every commit. Use --strict mode for comprehensive validation.

Unit Tests

Use helm-unittest to test template rendering with assertions.

Integration Tests

Define helm test hooks to verify deployments in a cluster.

Validate Manifests

Use kubeconform or kubectl apply --dry-run to validate generated manifests.

Dry Run

Always use --dry-run --debug before actual installation.

CI/CD Testing

Run linting, unit tests, and validation in CI/CD pipelines.
# Linting helm lint ./my-chart --strict helm lint ./my-chart -f values-prod.yaml # Unit testing with helm-unittest helm plugin install https://github.com/helm-unittest/helm-unittest helm unittest ./my-chart # Test file example # tests/deployment_test.yaml suite: test deployment templates: - deployment.yaml tests: - it: should render deployment asserts: - isKind: of: Deployment - equal: path: spec.replicas value: 3 - it: should set image from values values: - image: repository: nginx tag: "1.25" asserts: - equal: path: spec.template.spec.containers[0].image value: nginx:1.25 # helm test (integration) helm test my-release helm test my-release --logs # Validate with kubeconform helm template my-release ./my-chart | kubeconform -strict # Validate with kubectl helm template my-release ./my-chart | kubectl apply --dry-run=client -f - # Dry run with debug helm install my-release ./my-chart --dry-run --debug # CI/CD pipeline example stages: - lint - unit-test - validate - deploy lint: script: - helm lint ./my-chart --strict unit-test: script: - helm unittest ./my-chart validate: script: - helm template test ./my-chart | kubeconform -strict deploy: script: - helm upgrade --install my-release ./my-chart --atomic --wait
Testing Best Practices:
  • Lint charts before every commit
  • Write unit tests for critical templates
  • Define integration tests with helm test
  • Validate manifests with kubeconform
  • Use dry-run before deployment
  • Automate testing in CI/CD
Production Deployment Best Practices

Use --atomic

Automatic rollback on failure. Ensures stable state after deployments.

Set --timeout

Set appropriate timeouts for large deployments. Default is 5 minutes.

Use --wait

Wait for resources to be ready before marking deployment successful.

Control History

Use --history-max to manage release history and storage.

GitOps Integration

Use ArgoCD or Flux for GitOps deployments. Git as source of truth.

Monitor Deployments

Monitor application health after deployment. Set up alerts and dashboards.
# Production deployment command helm upgrade --install my-release ./my-chart \ --namespace production \ --create-namespace \ -f values-prod.yaml \ --atomic \ --wait \ --timeout 15m \ --history-max 20 # GitOps with ArgoCD apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-app namespace: argocd spec: project: default source: repoURL: https://github.com/myorg/helm-charts targetRevision: HEAD path: charts/my-app helm: valueFiles: - values-prod.yaml destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true # Monitoring after deployment kubectl get pods -n production -l app=my-app kubectl get svc -n production -l app=my-app kubectl rollout status deployment/my-app -n production # Rollback if needed helm history my-release -n production helm rollback my-release 2 -n production --wait # Health checks kubectl get events -n production --field-selector involvedObject.name=my-app # Production checklist # ✓ Tested in staging # ✓ Values validated # ✓ Secrets configured # ✓ Backup in place # ✓ Rollback plan ready # ✓ Monitoring enabled # ✓ Alerts configured
Production Deployment Checklist:
  • Test in staging first
  • Validate values and schema
  • Configure secrets properly
  • Back up release secrets
  • Have a rollback plan
  • Enable monitoring and alerts
  • Use --atomic for automatic rollback
  • Set appropriate timeouts
Helm Best Practices Summary
# Helm Best Practices Checklist ## Chart Structure ☐ Use standard directory structure ☐ Name charts consistently ☐ Document in README.md ☐ Use helpers for reusable code ☐ Keep templates focused ☐ Include values.schema.json ## Values Management ☐ Organize values logically ☐ Provide production-ready defaults ☐ Use environment-specific values files ☐ Never store secrets in values.yaml ☐ Use schema validation ☐ Use global values for shared config ## Versioning ☐ Follow semantic versioning ☐ Keep chart and app versions separate ☐ Pin dependency versions ☐ Tag releases in Git ☐ Maintain a changelog ☐ Limit release history ## Security ☐ Never store secrets in values.yaml ☐ Use external secret management ☐ Apply least privilege RBAC ☐ Sign and verify charts ☐ Scan for vulnerabilities ☐ Define security contexts ☐ Enforce policies with OPA ## Testing ☐ Lint charts before commit ☐ Write unit tests ☐ Define integration tests ☐ Validate manifests ☐ Use dry-run before deployment ☐ Automate testing in CI/CD ## Production Deployment ☐ Use --atomic for automatic rollback ☐ Set appropriate timeouts ☐ Use --wait for readiness ☐ Control history with --history-max ☐ Use GitOps for deployments ☐ Monitor deployments ☐ Have a rollback plan
Frequently Asked Questions
What is the most important Helm best practice?
Never store secrets in values.yaml. Use external secret management (Sealed Secrets, helm-secrets, External Secrets Operator) for all sensitive data.
How should I organize values for multiple environments?
Use a base values.yaml with defaults, then environment-specific files (values-dev.yaml, values-staging.yaml, values-prod.yaml) that override only what's needed.
What is the difference between chart version and appVersion?
Chart version refers to the Helm chart itself (changes when chart structure changes). appVersion refers to the application being deployed (changes when the app changes).
How do I test Helm charts properly?
Use a combination: helm lint for syntax, helm unittest for template rendering, helm test for integration, and kubeconform for Kubernetes API validation.
What flags should I use for production deployments?
Use --atomic (automatic rollback), --wait (wait for readiness), --timeout (sufficient timeout), and --history-max (manage history).
How do I secure my Helm charts?
Use external secret management, least privilege RBAC, chart signing, vulnerability scanning, security contexts, and policy enforcement with OPA/Gatekeeper.
Should I use Helm or Kustomize?
Both have their place. Use Helm for packaging and sharing applications. Use Kustomize for simple configuration overlays. Many teams use both together.
How do I implement GitOps with Helm?
Store charts in Git, use ArgoCD or Flux to sync them to clusters. Changes are made via Git commits, not manual Helm commands. Rollback is a Git revert.
Previous: Interview Questions Next: Helm Cheatsheet

Following Helm best practices ensures your charts are maintainable, secure, reusable, and production-ready. Start with the basics and gradually adopt more advanced practices as your charts mature.