Helm, Kustomize and GitOps with Argo CD · Helm Charts That Don't Surprise You
Broken YAML from Template Indentation
toYaml without the right indentation renders manifests that are invalid or, worse, valid but wrong; render and validate in CI.
Scenario
Example company: someone adds custom pod annotations and environment variables to a chart template. The chart installs fine in development, where those values are empty.
What you see in production
The production install fails with error converting YAML to JSON: yaml: line 32: did not find expected key, or, in another chart, succeeds while the environment variables silently land in the wrong place in the manifest.
The naive code
containers:
- name: app
env:
{{ toYaml .Values.env }} # not indented: items land at column 0
Reproduce it
Render a template with the indentation bug and with nindent, and validate both with kubeconform:
mkdir -p chart/templates
printf 'apiVersion: v2\nname: app\nversion: 0.1.0\n' > chart/Chart.yaml
printf 'env:\n - name: LOG_LEVEL\n value: info\n - name: REGION\n value: eu-west-1\n' > chart/values.yaml
write() {
cat > chart/templates/deploy.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata: {name: app}
spec:
selector: {matchLabels: {app: app}}
template:
metadata: {labels: {app: app}}
spec:
containers:
- name: app
image: example/app:1.0.0
env:
$1
EOF
}
write '{{ toYaml .Values.env }}'
echo "--- without indentation:"
helm template app ./chart 2>&1 | head -1 | cut -c1-120
write ' {{- toYaml .Values.env | nindent 12 }}'
echo "--- with nindent 12:"
helm template app ./chart | sed -n '/env:/,$p'
helm template app ./chart | kubeconform -strict -summary - | sed 's/Summary: //'
Output
--- without indentation:
Error: YAML parse error on app/templates/deploy.yaml: error converting YAML to JSON: yaml: line 12: did not find expecte
--- with nindent 12:
env:
- name: LOG_LEVEL
value: info
- name: REGION
value: eu-west-1
1 resource found parsing stdin - Valid: 1, Invalid: 0, Errors: 0, Skipped: 0Run this on your computer — see “Get Started” for setup.
Root cause
Go templates produce text, and YAML structure depends on indentation. toYaml outputs a block starting at column 0; inserted under a nested key it either breaks parsing or attaches the content to a different parent. Bugs hide when the value is empty in the environment where the chart was tested.
The fix
- Use
{{- toYaml .Values.x | nindent N }}(the-trims the preceding newline,nindentadds a newline and indents). - Wrap optional blocks in
{{- with .Values.x }}...{{- end }}so empty values render nothing. - In CI, render with representative values for every environment (including non-empty optional blocks) and validate with
kubeconform -strict. - Add
helm unittesttests for templates with logic.
Trade-offs
- Templating YAML is inherently fragile; keep templates simple and push complexity into values structure.
- Strict validation needs schemas for CRDs (kubeconform supports CRD schema catalogs).
Detect it in production
- CI renders that fail parsing or schema validation.
- Manifests in the cluster missing fields set in values (configuration drift checks).
Key points
- Templates output text; YAML depends on indentation.
- Use
toYaml | nindentandwithblocks for optional values. - Render every environment's values in CI and validate strictly.
Further reading: Helm template function list: nindent · Chart best practices: templates
Practice questions
What does {{- toYaml .Values.env | nindent 12 }} do?
Answer
Trims whitespace before the action, converts the value to YAML, then starts it on a new line indented by 12 spaces so it nests correctly under the parent key.
Why do template indentation bugs often reach production?
Answer
They only show when the optional value is non-empty; if development and CI use empty values, the broken branch is never rendered.
How do you catch rendering bugs in CI?
Answer
Render the chart with each environment's values (helm template) and validate the output strictly (kubeconform), plus helm unittest for template logic.
Open in the app to mark lessons complete, take notes and track your progress.