Debugging Helm Charts
Dry-Run
Debug Flags
Template Rendering
Troubleshooting
A comprehensive guide to debugging Helm charts covering dry-run, debug flags, template rendering issues, and troubleshooting techniques for Helm deployments.
Dry-Run
Debug Flags
Template Rendering
Troubleshooting
Why Debugging Helm Charts Matters
Debugging Helm charts is an essential skill for chart developers and operators. Helm charts involve multiple layers of complexity: Go templates, values, dependencies, and Kubernetes manifests. When something goes wrong, you need systematic debugging techniques to identify and fix the issue.
Common debugging scenarios include:
- Template Errors: Syntax errors, missing functions, or incorrect values
- Rendering Issues: Templates not producing expected output
- Value Problems: Values not being applied correctly
- Dependency Issues: Subcharts not rendering as expected
- Deployment Failures: Charts install but fail to deploy
- Hook Failures: Pre/post-install hooks failing
Key Principle: Always debug with --dry-run and --debug before deploying to production. This helps catch issues early without affecting the cluster.
Dry-Run: Preview Before Deploy
--dry-run renders the templates and shows what would be deployed without actually applying anything to the cluster. This is the first step in debugging.
# Basic dry-run
helm install my-release ./my-chart --dry-run
# Dry-run with debug output
helm install my-release ./my-chart --dry-run --debug
# Dry-run for upgrade
helm upgrade my-release ./my-chart --dry-run
# Dry-run with specific values
helm install my-release ./my-chart --dry-run -f values-prod.yaml
# Dry-run with --set
helm install my-release ./my-chart --dry-run --set replicaCount=5
# Dry-run for template rendering only
helm template my-release ./my-chart
# Template with debug
helm template my-release ./my-chart --debug
# Dry-run for linting
helm lint ./my-chart
# Dry-run for uninstall
helm uninstall my-release --dry-run
# Dry-run for rollback
helm rollback my-release 2 --dry-run
# Dry-run with values from multiple sources
helm install my-release ./my-chart --dry-run \
-f values-base.yaml \
-f values-prod.yaml \
--set image.tag=1.2.3
# Dry-run and save output to file
helm template my-release ./my-chart > manifests.yaml
# Dry-run and validate with kubectl
helm template my-release ./my-chart | kubectl apply --dry-run=client -f -
# Dry-run and validate with kubeconform
helm template my-release ./my-chart | kubeconform -strict
# Dry-run with server-side validation
helm install my-release ./my-chart --dry-run --debug 2>&1 | head -100
helm install --dry-run
Preview installation without applying. Shows all resources that would be created.
helm upgrade --dry-run
Preview upgrade without applying. Shows what would change.
helm template
Render templates locally without cluster access. Ideal for offline debugging.
helm lint
Validate chart syntax and best practices without rendering.
Dry-Run Best Practices:
- Always use
--dry-run before actual deployment
- Combine with
--debug for detailed output
- Validate rendered manifests with
kubeconform or kubectl
- Test with all value combinations
- Save output for comparison
Debug Flags
# --debug flag
# Shows detailed output including rendered templates and values
helm install my-release ./my-chart --debug
# --debug with dry-run
helm install my-release ./my-chart --dry-run --debug
# --debug with template
helm template my-release ./my-chart --debug
# --debug with upgrade
helm upgrade my-release ./my-chart --debug
# --debug with lint
helm lint ./my-chart --debug
# Verbose output
helm install my-release ./my-chart --debug 2>&1 | tee debug.log
# Filter debug output
helm install my-release ./my-chart --debug 2>&1 | grep -i error
# Debug with JSON output
helm template my-release ./my-chart --debug --output json
# Debug with YAML output
helm template my-release ./my-chart --debug --output yaml
# Debug specific template
helm template my-release ./my-chart --debug --show-only templates/deployment.yaml
# Debug with values
helm install my-release ./my-chart --debug --set debug=true
# Debug hooks
helm install my-release ./my-chart --debug --no-hooks # Skip hooks
helm install my-release ./my-chart --debug --hooks # Include hooks
# Debug with timeout
helm install my-release ./my-chart --debug --timeout 10m
# Debug with atomic
helm install my-release ./my-chart --debug --atomic
--debug
Shows rendered templates, computed values, and internal Helm operations.
--show-only
Show only specific templates in output. Useful for focused debugging.
--output
Specify output format (yaml, json, table). Useful for parsing.
--timeout
Set timeout for operations. Helps debug timeout-related issues.
Debug Output Sections:
- USER-SUPPLIED VALUES: Values from command line and files
- COMPUTED VALUES: Final values after defaults and overrides
- HOOKS: Hook execution details
- MANIFEST: Final rendered Kubernetes manifests
- NOTES: Post-installation notes
Template Rendering Issues
# Common template rendering issues and solutions
# 1. Missing values (nil pointer error)
# Error: nil pointer evaluating interface {}.key
# Solution: Use default or with block
# Bad:
{{ .Values.database.host }} # Fails if database is nil
# Good:
{{ .Values.database.host | default "localhost" }}
{{- with .Values.database }}
host: {{ .host }}
{{- end }}
# 2. Whitespace issues
# Error: YAML parse error due to incorrect indentation
# Bad:
metadata:
name: {{ .Values.name }}
labels:
{{- toYaml .Values.labels }}
# Good:
metadata:
name: {{ .Values.name }}
labels:
{{- toYaml .Values.labels | nindent 4 }}
# 3. Quote issues
# Error: YAML parse error due to special characters
# Bad:
value: {{ .Values.password }} # Fails if password contains special chars
# Good:
value: {{ .Values.password | quote }}
# 4. Type conversion issues
# Error: wrong type for value
# Bad:
replicas: {{ .Values.replicaCount }} # Fails if string
# Good:
replicas: {{ .Values.replicaCount | int }}
# 5. Missing required values
# Error: required value missing
# Solution: Use required function
{{ required "A valid image.repository is required" .Values.image.repository }}
# 6. Loop issues
# Error: range can't iterate over nil
# Bad:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value }}
{{- end }}
# Good:
{{- range .Values.env | default (list) }}
- name: {{ .name }}
value: {{ .value }}
{{- end }}
# 7. Template function errors
# Error: function "foo" not defined
# Solution: Check function name and scope
{{ include "my-app.labels" . }} # Correct
{{ template "my-app.labels" . }} # Also works
# 8. Indentation issues with toYaml
# Error: YAML parse error
# Bad:
resources:
{{ toYaml .Values.resources }}
# Good:
resources:
{{- toYaml .Values.resources | nindent 2 }}
# 9. Conditional rendering issues
# Bad:
{{ if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
{{ end }}
# Good:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
{{- end }}
# 10. Scope issues in range
# Bad:
{{ range .Values.hosts }}
host: {{ . }} # Can't access .Values inside range
{{ end }}
# Good:
{{ range .Values.hosts }}
host: {{ . }}
port: {{ $.Values.service.port }} # Use $ for root scope
{{ end }}
# Debugging template rendering
# 1. Use --show-only to isolate template
helm template my-release ./my-chart --show-only templates/deployment.yaml --debug
# 2. Use printf to debug values
{{ printf "Values: %v" .Values | quote }}
# 3. Use toYaml to inspect values
{{ toYaml .Values | nindent 2 }}
# 4. Use typeOf to check types
{{ typeOf .Values.replicaCount }}
# 5. Use kindOf to check kinds
{{ kindOf .Values.replicaCount }}
# 6. Use fail to debug
{{ fail "Debug message" }}
# 7. Use required for validation
{{ required "Database host is required" .Values.database.host }}
# 8. Use empty to check for nil
{{ if empty .Values.optional }}No value{{ end }}
# 9. Use ternary for conditionals
{{ ternary "enabled" "disabled" .Values.feature.enabled }}
# 10. Use coalesce for defaults
{{ coalesce .Values.primary .Values.secondary "default" }}
Common Template Errors:
- nil pointer: Accessing values that don't exist
- YAML parse error: Incorrect indentation or missing quotes
- function not defined: Typo in function name or missing helper
- range can't iterate: Trying to loop over non-list value
- wrong type: Using string where integer expected
- required value missing: Mandatory value not provided
Advanced Debugging Techniques
# 1. Debug with values dump
# Add this to a template to dump all values
{{- define "debug.values" -}}
{{- toYaml .Values | nindent 0 }}
{{- end }}
# Call it with:
{{ include "debug.values" . }}
# 2. Debug with specific value inspection
{{- define "debug.inspect" -}}
Key: {{ .key }}
Value: {{ .value }}
Type: {{ typeOf .value }}
{{- end }}
# 3. Debug with conditional output
{{- if .Values.debug }}
# Debug output
Values: {{ .Values | toYaml }}
{{- end }}
# 4. Debug with fail for validation
{{- if not .Values.required }}
{{- fail "required value is required" }}
{{- end }}
# 5. Debug with printf
{{ printf "Replica count: %d" .Values.replicaCount }}
{{ printf "Image: %s:%s" .Values.image.repository .Values.image.tag }}
# 6. Debug with keys (list all keys)
{{ keys .Values | sortAlpha }}
# 7. Debug with hasKey
{{ if hasKey .Values "database" }}
Database configuration exists
{{ end }}
# 8. Debug with empty check
{{ if empty .Values.optional }}
Optional value is empty
{{ else }}
Optional value: {{ .Values.optional }}
{{ end }}
# 9. Debug with coalesce
{{ coalesce .Values.primary .Values.secondary .Values.tertiary "default" }}
# 10. Debug with ternary
{{ ternary "yes" "no" .Values.enabled }}
# 11. Debug with fromYaml/toYaml round-trip
{{ .Values.complex | toYaml | fromYaml | toYaml }}
# 12. Debug with b64enc/b64dec
{{ .Values.data | b64enc }}
{{ .Values.encoded | b64dec }}
# 13. Debug with sha256sum
{{ .Values.content | sha256sum }}
# 14. Debug with regexMatch
{{ if regexMatch "^[a-z]+$" .Values.name }}
Valid name
{{ end }}
# 15. Debug with regexReplaceAll
{{ .Values.name | regexReplaceAll "[^a-z]" "" }}
# 16. Debug with uuidv4
{{ uuidv4 }}
# 17. Debug with randAlphaNum
{{ randAlphaNum 10 }}
# 18. Debug with env
{{ env "HOME" }}
# 19. Debug with expandenv
{{ expandenv "$HOME" }}
# 20. Debug with fail for testing
{{ fail "Test error message" }}
# 21. Debug with required
{{ required "This value is required" .Values.mandatory }}
# 22. Debug with tpl (render string as template)
{{ tpl .Values.template . }}
# 23. Debug with include (reuse templates)
{{ include "my-app.labels" . }}
# 24. Debug with template (direct rendering)
{{ template "my-app.labels" . }}
# 25. Debug with lookup (query cluster)
{{ lookup "v1" "Secret" .Release.Namespace "my-secret" }}
Debug Values
Dump all values to inspect what Helm is working with.
printf Debugging
Print values with type information for inspection.
Validation
Use required and fail for value validation.
Lookup
Query existing cluster resources for debugging.
Debugging Hooks
# Debug hooks in templates
# 1. Check hook annotations
metadata:
annotations:
helm.sh/hook: pre-install
helm.sh/hook-weight: "5"
helm.sh/hook-delete-policy: hook-succeeded
# 2. Debug hook execution
helm install my-release ./my-chart --debug --no-hooks # Skip hooks
helm install my-release ./my-chart --debug # Include hooks
# 3. Debug hook failures
kubectl get jobs -n default | grep -E "pre-install|post-install"
kubectl logs
# 4. Debug hook policies
# hook-succeeded: Delete after success
# hook-failed: Delete after failure
# before-hook-creation: Delete before new hook
# 5. Debug hook weights
# Lower weight = earlier execution
helm.sh/hook-weight: "1" # Executes first
helm.sh/hook-weight: "5" # Executes later
# 6. Debug hook types
# pre-install, post-install
# pre-upgrade, post-upgrade
# pre-delete, post-delete
# pre-rollback, post-rollback
# test
# 7. Debug hook resources
# Hooks can be Jobs, Pods, or any Kubernetes resource
# 8. Debug hook cleanup
helm.sh/hook-delete-policy: hook-succeeded,hook-failed
# Always clean up hooks
# 9. Debug hook timeout
helm install my-release ./my-chart --timeout 10m --debug
# 10. Debug hook logs
kubectl logs -f
# 11. Debug hook status
kubectl describe job
# 12. Debug hook events
kubectl get events --field-selector involvedObject.kind=Job
# 13. Debug hook with dry-run
helm install my-release ./my-chart --dry-run --debug
# 14. Debug hook with no-hooks
helm install my-release ./my-chart --no-hooks --debug
# 15. Debug hook with atomic
helm install my-release ./my-chart --atomic --debug
Hook Debugging Tips:
- Use
--debug to see hook execution order
- Use
--no-hooks to temporarily disable hooks
- Check hook logs with
kubectl logs
- Verify hook delete policies
- Check hook weights for ordering issues
- Use
--timeout for long-running hooks
Common Errors and Solutions
# Error 1: nil pointer evaluating interface {}.key
# Cause: Accessing a value that doesn't exist
# Solution: Use default or with block
# Fix:
{{ .Values.database.host | default "localhost" }}
# Error 2: YAML parse error on templates/deployment.yaml
# Cause: Incorrect indentation
# Solution: Use nindent or indent correctly
# Fix:
{{- toYaml .Values.resources | nindent 10 }}
# Error 3: function "include" not defined
# Cause: Missing helper template or typo
# Solution: Check _helpers.tpl
# Fix:
{{ include "my-app.fullname" . }}
# Error 4: range can't iterate over
# Cause: Trying to range over non-list value
# Solution: Use default list or check type
# Fix:
{{- range .Values.env | default (list) }}
# Error 5: error calling include: template: no template "my-app.labels" associated with template "gotpl"
# Cause: Missing helper template
# Solution: Add helper to _helpers.tpl
# Fix:
{{- define "my-app.labels" -}}
app: {{ include "my-app.name" . }}
{{- end }}
# Error 6: wrong type for value; expected string; got int
# Cause: Type mismatch
# Solution: Use quote or toString
# Fix:
value: {{ .Values.port | quote }}
# Error 7: UPGRADE FAILED: cannot patch "my-app" with kind Deployment
# Cause: Immutable field changed
# Solution: Use --force to recreate
# Fix:
helm upgrade my-release ./my-chart --force
# Error 8: timed out waiting for the condition
# Cause: Resources not ready in time
# Solution: Increase timeout
# Fix:
helm install my-release ./my-chart --timeout 15m
# Error 9: no matches for kind "CustomResource" in version "example.com/v1"
# Cause: CRD not installed
# Solution: Install CRD first
# Fix:
kubectl apply -f crds/
helm install my-release ./my-chart
# Error 10: rendered manifests contain a resource that already exists
# Cause: Resource conflict
# Solution: Use --force or clean up
# Fix:
kubectl delete --ignore-not-found
helm install my-release ./my-chart
# Error 11: failed to download chart
# Cause: Repository not added or network issue
# Solution: Add repository or check network
# Fix:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# Error 12: chart requires kubeVersion
# Cause: Kubernetes version mismatch
# Solution: Upgrade Kubernetes or use compatible chart
# Fix:
helm install my-release ./my-chart --kube-version 1.28.0
# Error 13: values don't meet the specifications of the schema
# Cause: Invalid values
# Solution: Check values.schema.json
# Fix:
helm lint ./my-chart --strict
# Error 14: release named my-release already exists
# Cause: Release name conflict
# Solution: Use different name or uninstall
# Fix:
helm uninstall my-release
helm install my-release ./my-chart
# Error 15: cannot re-use a name that is still in use
# Cause: Release in failed state
# Solution: Uninstall or use --force
# Fix:
helm uninstall my-release --keep-history
# or
helm upgrade my-release ./my-chart --force
General Debugging Tips:
- Always start with
--dry-run --debug
- Isolate templates with
--show-only
- Use
helm template for local rendering
- Validate with
kubeconform or kubectl apply --dry-run
- Check logs with
--debug 2>&1 | grep error
- Test values with different combinations
- Use
helm lint for syntax validation
Frequently Asked Questions
What is the difference between --dry-run and --debug?
--dry-run simulates the installation without applying changes. --debug provides detailed output about what Helm is doing. Use both together for comprehensive debugging.
How do I debug a specific template?
Use helm template my-release ./my-chart --show-only templates/deployment.yaml --debug to render only that template with debug output.
How do I fix nil pointer errors?
Use default to provide fallback values, with to check if a value exists, or required to enforce mandatory values.
How do I debug YAML indentation issues?
Use nindent or indent correctly with toYaml. Always check the rendered output with --debug.
How do I debug hooks?
Use --debug to see hook execution. Check hook logs with kubectl logs. Use --no-hooks to temporarily disable hooks.
How do I validate rendered manifests?
Use helm template my-release ./my-chart | kubectl apply --dry-run=client -f - or helm template my-release ./my-chart | kubeconform -strict.
What should I check first when debugging?
Start with helm lint for syntax, then helm template --debug for rendering, and finally helm install --dry-run --debug for full simulation.
How do I debug values not being applied?
Use --debug to see computed values. Check the order of values files and --set flags. Use helm get values for installed releases.
Debugging Helm charts is an essential skill. Use dry-run, debug flags, and systematic troubleshooting to identify and fix issues quickly and efficiently.