|
| 1 | +--- |
| 2 | +title: Templating |
| 3 | +sidebar: |
| 4 | + order: 11 |
| 5 | +--- |
| 6 | + |
| 7 | +This guide provides best practices for using [templating](/ref/templating/) in your Zarf packages. |
| 8 | + |
| 9 | +:::caution |
| 10 | + |
| 11 | +Templating with Package Values is currently an **alpha feature** and requires the `--features="values=true"` flag when creating, deploying, inspecting, or removing packages. |
| 12 | + |
| 13 | +::: |
| 14 | + |
| 15 | +## Use Pipelines for Readability |
| 16 | + |
| 17 | +Chain functions together using the pipe operator (`|`) for clarity: |
| 18 | + |
| 19 | +```yaml |
| 20 | +# ✅ GOOD: Clear transformation pipeline |
| 21 | +name: {{ .Values.app.name | lower | kebabcase | trunc 50 }} |
| 22 | + |
| 23 | +# ❌ AVOID: Nested function calls |
| 24 | +name: {{ trunc 50 (kebabcase (lower .Values.app.name)) }} |
| 25 | +``` |
| 26 | +
|
| 27 | +## Always Quote String Values |
| 28 | +
|
| 29 | +Use the `quote` function to ensure string values are properly quoted in YAML: |
| 30 | + |
| 31 | +```yaml |
| 32 | +# ✅ GOOD: Properly quoted |
| 33 | +environment: {{ .Values.app.environment | quote }} |
| 34 | +image: {{ .Values.app.image | quote }} |
| 35 | +
|
| 36 | +# ❌ RISKY: Can cause YAML parsing issues |
| 37 | +environment: {{ .Values.app.environment }} |
| 38 | +``` |
| 39 | + |
| 40 | +**Why this matters:** |
| 41 | +- Prevents YAML parsing errors with special characters |
| 42 | +- Handles values like `"yes"`, `"no"`, `"true"`, `"false"` correctly |
| 43 | +- Protects against values that look like numbers |
| 44 | + |
| 45 | +**Example of the problem:** |
| 46 | + |
| 47 | +```yaml |
| 48 | +# Without quote, this breaks YAML parsing |
| 49 | +apiVersion: {{ .Values.version }} # If version is "1.0", breaks |
| 50 | +apiVersion: {{ .Values.version | quote }} # "1.0" works correctly |
| 51 | +``` |
| 52 | + |
| 53 | +## Provide Defaults for Optional Values |
| 54 | + |
| 55 | +Use the `default` function to provide fallback values: |
| 56 | + |
| 57 | +```yaml |
| 58 | +# ✅ GOOD: Safe with defaults |
| 59 | +replicas: {{ .Values.app.replicas | default 3 }} |
| 60 | +namespace: {{ .Values.app.namespace | default "default" | quote }} |
| 61 | +logLevel: {{ .Values.app.logLevel | default "info" | quote }} |
| 62 | +
|
| 63 | +# ❌ RISKY: Fails if value is missing |
| 64 | +replicas: {{ .Values.app.replicas }} |
| 65 | +``` |
| 66 | + |
| 67 | +**When to use defaults:** |
| 68 | +- Optional configuration values |
| 69 | +- Values that have sensible fallbacks |
| 70 | +- Any value that might not be provided by users |
| 71 | + |
| 72 | +**Combine with other functions:** |
| 73 | + |
| 74 | +```yaml |
| 75 | +# Default, then transform |
| 76 | +environment: {{ .Values.app.environment | default "production" | upper | quote }} |
| 77 | +``` |
| 78 | + |
| 79 | +## Control Whitespace |
| 80 | + |
| 81 | +Use whitespace control operators (`-`) to manage output formatting: |
| 82 | + |
| 83 | +```yaml |
| 84 | +# ✅ GOOD: Clean output, no extra blank lines |
| 85 | +{{- if .Values.app.debug }} |
| 86 | +debug: true |
| 87 | +{{- end }} |
| 88 | +
|
| 89 | +# ❌ PRODUCES UNWANTED BLANK LINES |
| 90 | +{{ if .Values.app.debug }} |
| 91 | +debug: true |
| 92 | +{{ end }} |
| 93 | +``` |
| 94 | + |
| 95 | +**Whitespace control operators:** |
| 96 | +- `{{-` removes whitespace before the template |
| 97 | +- `-}}` removes whitespace after the template |
| 98 | +- `{{- -}}` removes whitespace on both sides |
| 99 | + |
| 100 | +**Example output difference:** |
| 101 | + |
| 102 | +```yaml |
| 103 | +# Without whitespace control: |
| 104 | +metadata: |
| 105 | + labels: |
| 106 | +
|
| 107 | + app: nginx |
| 108 | +
|
| 109 | +# With whitespace control: |
| 110 | +metadata: |
| 111 | + labels: |
| 112 | + app: nginx |
| 113 | +``` |
| 114 | + |
| 115 | +## Comment Complex Template Logic |
| 116 | + |
| 117 | +Add comments to explain non-obvious template logic: |
| 118 | + |
| 119 | +```yaml |
| 120 | +# ✅ GOOD: Explained logic |
| 121 | +# Calculate scaled replica count based on environment |
| 122 | +# Production gets 3x replicas, non-production gets 1x |
| 123 | +{{- $baseReplicas := .Values.app.replicas | default 1 -}} |
| 124 | +{{- $scaleFactor := .Values.app.production | ternary 3 1 -}} |
| 125 | +replicas: {{ mul $baseReplicas $scaleFactor }} |
| 126 | +
|
| 127 | +# ❌ UNCLEAR: What's happening here? |
| 128 | +{{- $baseReplicas := .Values.app.replicas | default 1 -}} |
| 129 | +{{- $scaleFactor := .Values.app.production | ternary 3 1 -}} |
| 130 | +replicas: {{ mul $baseReplicas $scaleFactor }} |
| 131 | +``` |
| 132 | + |
| 133 | +**What to comment:** |
| 134 | +- Complex conditionals |
| 135 | +- Non-obvious default values |
| 136 | +- Business logic encoded in templates |
| 137 | + |
| 138 | +## Avoid Over-Templating |
| 139 | + |
| 140 | +Keep templates simple and prefer data over complex logic: |
| 141 | + |
| 142 | +```yaml |
| 143 | +# ✅ GOOD: Simple conditional |
| 144 | +{{- if .Values.monitoring.enabled }} |
| 145 | +annotations: |
| 146 | + prometheus.io/scrape: "true" |
| 147 | +{{- end }} |
| 148 | +
|
| 149 | +# ❌ AVOID: Complex nested logic |
| 150 | +{{- if and .Values.monitoring.enabled (or (eq .Values.app.environment "production") (eq .Values.app.environment "staging")) (not .Values.app.debug) }} |
| 151 | +annotations: |
| 152 | + prometheus.io/scrape: "true" |
| 153 | + prometheus.io/port: {{ .Values.monitoring.port | default 8080 | quote }} |
| 154 | +{{- end }} |
| 155 | +``` |
| 156 | + |
| 157 | +**Better approach for complex cases:** |
| 158 | + |
| 159 | +Move logic to values files: |
| 160 | + |
| 161 | +```yaml |
| 162 | +# values.yaml |
| 163 | +monitoring: |
| 164 | + enabled: true |
| 165 | + annotations: |
| 166 | + prometheus.io/scrape: "true" |
| 167 | + prometheus.io/port: "8080" |
| 168 | +``` |
| 169 | + |
| 170 | +```yaml |
| 171 | +# manifest |
| 172 | +{{- if .Values.monitoring.enabled }} |
| 173 | +annotations: |
| 174 | +{{- range $key, $value := .Values.monitoring.annotations }} |
| 175 | + {{ $key }}: {{ $value | quote }} |
| 176 | +{{- end }} |
| 177 | +{{- end }} |
| 178 | +``` |
| 179 | + |
| 180 | +## Use Template Variables for Reusability |
| 181 | + |
| 182 | +Define template variables to avoid repeating complex expressions: |
| 183 | + |
| 184 | +```yaml |
| 185 | +# ✅ GOOD: Define once, use many times |
| 186 | +{{- $appName := .Values.app.name | lower | kebabcase -}} |
| 187 | +{{- $namespace := .Values.app.namespace | default "default" -}} |
| 188 | +
|
| 189 | +apiVersion: v1 |
| 190 | +kind: Service |
| 191 | +metadata: |
| 192 | + name: {{ $appName }}-service |
| 193 | + namespace: {{ $namespace }} |
| 194 | +spec: |
| 195 | + selector: |
| 196 | + app: {{ $appName }} |
| 197 | +--- |
| 198 | +apiVersion: apps/v1 |
| 199 | +kind: Deployment |
| 200 | +metadata: |
| 201 | + name: {{ $appName }}-deployment |
| 202 | + namespace: {{ $namespace }} |
| 203 | +spec: |
| 204 | + selector: |
| 205 | + matchLabels: |
| 206 | + app: {{ $appName }} |
| 207 | +
|
| 208 | +# ❌ AVOID: Repeating the same expression |
| 209 | +apiVersion: v1 |
| 210 | +kind: Service |
| 211 | +metadata: |
| 212 | + name: {{ .Values.app.name | lower | kebabcase }}-service |
| 213 | + namespace: {{ .Values.app.namespace | default "default" }} |
| 214 | +spec: |
| 215 | + selector: |
| 216 | + app: {{ .Values.app.name | lower | kebabcase }} |
| 217 | +``` |
| 218 | + |
| 219 | +## Handle Type Conversions Explicitly |
| 220 | + |
| 221 | +Convert types explicitly when needed: |
| 222 | + |
| 223 | +```yaml |
| 224 | +# ✅ GOOD: Explicit type conversion |
| 225 | +apiVersion: v1 |
| 226 | +kind: ConfigMap |
| 227 | +data: |
| 228 | + # Numbers must be strings in ConfigMaps |
| 229 | + port: {{ .Values.app.port | toString | quote }} |
| 230 | + replicas: {{ .Values.app.replicas | toString | quote }} |
| 231 | +
|
| 232 | + # Booleans to strings |
| 233 | + debug: {{ .Values.app.debug | toString | quote }} |
| 234 | +
|
| 235 | +# ❌ RISKY: Type mismatch errors |
| 236 | +data: |
| 237 | + port: {{ .Values.app.port }} # May fail if port is a number |
| 238 | +``` |
| 239 | + |
| 240 | +**Common conversions:** |
| 241 | +- `toString` - Convert to string |
| 242 | +- `toInt` - Convert to integer |
| 243 | +- `toYaml` - Convert object to YAML |
| 244 | +- `toJson` - Convert object to JSON |
| 245 | + |
| 246 | +## Test Templates Before Deployment |
| 247 | + |
| 248 | +Always preview your templates before deploying: |
| 249 | + |
| 250 | +```bash |
| 251 | +# Preview manifests with default values |
| 252 | +zarf dev inspect manifests --features="values=true" |
| 253 | +
|
| 254 | +# Preview with custom values |
| 255 | +zarf dev inspect manifests \ |
| 256 | + --features="values=true" \ |
| 257 | + -f custom-values.yaml |
| 258 | +
|
| 259 | +# Preview with inline overrides |
| 260 | +zarf dev inspect manifests \ |
| 261 | + --features="values=true" \ |
| 262 | + --set-values="app.replicas=5,app.environment=staging" |
| 263 | +``` |
| 264 | + |
| 265 | +**What to check:** |
| 266 | +- Templates render without errors |
| 267 | +- Output looks as expected |
| 268 | +- No unexpected whitespace |
| 269 | +- Values are correctly interpolated |
| 270 | +- Conditionals evaluate correctly |
| 271 | + |
| 272 | +## Handle Missing Values Gracefully |
| 273 | + |
| 274 | +Protect against missing or nil values: |
| 275 | + |
| 276 | +```yaml |
| 277 | +# ✅ GOOD: Safe against missing values |
| 278 | +{{- if .Values.monitoring }} |
| 279 | + {{- if .Values.monitoring.enabled }} |
| 280 | +annotations: |
| 281 | + prometheus.io/scrape: "true" |
| 282 | + {{- end }} |
| 283 | +{{- end }} |
| 284 | +
|
| 285 | +# Alternative: Use default for the entire object |
| 286 | +{{- $monitoring := .Values.monitoring | default dict -}} |
| 287 | +{{- if $monitoring.enabled }} |
| 288 | +annotations: |
| 289 | + prometheus.io/scrape: "true" |
| 290 | +{{- end }} |
| 291 | +
|
| 292 | +# ❌ RISKY: Will fail if monitoring is undefined |
| 293 | +{{- if .Values.monitoring.enabled }} |
| 294 | +annotations: |
| 295 | + prometheus.io/scrape: "true" |
| 296 | +{{- end }} |
| 297 | +``` |
| 298 | + |
| 299 | +## Be Consistent with Indentation |
| 300 | + |
| 301 | +Use the `indent` and `nindent` functions consistently: |
| 302 | + |
| 303 | +```yaml |
| 304 | +# ✅ GOOD: Proper indentation |
| 305 | +apiVersion: v1 |
| 306 | +kind: ConfigMap |
| 307 | +data: |
| 308 | + config.yaml: | |
| 309 | +{{ .Values.app.config | toYaml | indent 4 }} |
| 310 | +
|
| 311 | +# ✅ ALSO GOOD: Using nindent (newline + indent) |
| 312 | +apiVersion: v1 |
| 313 | +kind: ConfigMap |
| 314 | +data: |
| 315 | + config.yaml: |- |
| 316 | +{{- .Values.app.config | toYaml | nindent 4 }} |
| 317 | +
|
| 318 | +# ❌ AVOID: Inconsistent indentation |
| 319 | +apiVersion: v1 |
| 320 | +kind: ConfigMap |
| 321 | +data: |
| 322 | + config.yaml: | |
| 323 | + {{ .Values.app.config | toYaml }} |
| 324 | +``` |
| 325 | + |
| 326 | +**Choose the right function:** |
| 327 | +- `indent N` - Indent every line by N spaces |
| 328 | +- `nindent N` - Add newline, then indent by N spaces |
| 329 | + |
| 330 | +## Common Errors and Solutions |
| 331 | + |
| 332 | +### Template Parse Errors |
| 333 | + |
| 334 | +**Error:** |
| 335 | +``` |
| 336 | +Error: template: :5:14: executing "" at <.Values.app.missing>: map has no entry for key "missing" |
| 337 | +``` |
| 338 | + |
| 339 | +**Solution:** |
| 340 | +```yaml |
| 341 | +# Use default for optional values |
| 342 | +value: {{ .Values.app.missing | default "fallback" }} |
| 343 | + |
| 344 | +# Or check if exists first |
| 345 | +{{- if .Values.app.missing }} |
| 346 | +value: {{ .Values.app.missing }} |
| 347 | +{{- end }} |
| 348 | +``` |
| 349 | + |
| 350 | +### Type Errors |
| 351 | + |
| 352 | +**Error:** |
| 353 | +``` |
| 354 | +Error: wrong type for value; expected string; got int |
| 355 | +``` |
| 356 | + |
| 357 | +**Solution:** |
| 358 | +```yaml |
| 359 | +# Convert types explicitly |
| 360 | +value: {{ .Values.app.port | toString | quote }} |
| 361 | +``` |
| 362 | + |
| 363 | +### Whitespace Issues |
| 364 | + |
| 365 | +**Problem:** Extra blank lines or spacing in output |
| 366 | + |
| 367 | +**Solution:** |
| 368 | +```yaml |
| 369 | +# Use whitespace control operators |
| 370 | +{{- if .Values.app.debug -}} |
| 371 | +debug: true |
| 372 | +{{- end -}} |
| 373 | +``` |
| 374 | + |
| 375 | +### Quoting Problems |
| 376 | + |
| 377 | +**Problem:** YAML parsing fails with boolean-like strings |
| 378 | + |
| 379 | +**Solution:** |
| 380 | +```yaml |
| 381 | +# Always quote string values |
| 382 | +value: {{ .Values.app.setting | quote }} |
| 383 | +``` |
| 384 | + |
| 385 | +## Related Documentation |
| 386 | + |
| 387 | +- [Templating Reference](/ref/templating/) - Complete templating documentation |
| 388 | +- [Package Values](/ref/package-values/) - Defining and using package values |
| 389 | +- [Sprig Function Documentation](http://masterminds.github.io/sprig/) - Complete Sprig reference |
| 390 | +- [values-templating Example](https://github.com/defenseunicorns/zarf/tree/main/examples/values-templating) - Working examples |
0 commit comments