Skip to navigation

Templates and heredocs

Templates render during compilation. Only resulting values and asset bytes enter the bundle; applying needs neither a template engine nor source files. Use jsonencode or yamlencode for structured formats, and templates for formats such as INI or unit files.

Inline templates and heredocs

Quoted strings and heredocs use the same renderer. ${expression} converts strings, finite numbers, and booleans to text. Null, collections, and handles require explicit encoding or conversion. <<EOF preserves indentation; <<-EOF permits indentation stripping.

variable "enabled" {
type = bool
default = true
}
file "settings" {
path = "/etc/rivet-template.conf"
content = <<-EOF
# Managed by Rivet
enabled=${var.enabled}
%{ if var.enabled ~}
%{ for name in ["app", "ops"] ~}
account=${name}
%{ endfor ~}
%{ else ~}
disabled=true
%{ endif ~}
EOF
}

File templates

Create configuration/assets/config.tftpl:

assets/config.tftpl
# Managed by Rivet
%{ for key, value in settings ~}
${key}=${value}
%{ endfor ~}

Call it from configuration/main.hcl:

locals {
settings = { address = cidrhost("192.0.2.0/24", 10), enabled = true }
}
file "settings" {
path = "/etc/rivet-template.ini"
content = templatefile("assets/config.tftpl", { settings = local.settings })
mode = "0644"
}

templatefile(path, vars) exposes only the string-keyed vars map, accessed as bare names such as settings. It does not implicitly expose var, local, resources, facts, or caller loop variables. Pass anything it needs in vars. Functions are still available. Local reads resolve against the declaring expression’s source root, including inside child modules, and preserve source provenance when a resource attribute supplies a template string. Template files are regular UTF-8 files subject to the confined read rules; recursive inclusion fails.

The repository fixture is runnable without host mutation:

rivet validate examples/templates
rivet compile examples/templates --facts examples/file/target.json --out templates.bundle

String templates and escapes

templatestring(text, vars) uses the same isolated scope and accepts evaluated strings directly. This differs from Terraform’s reference-only restriction. Quoted HCL text undergoes its own rendering pass, so escape markers intended for the second pass:

file "greeting" {
path = "/etc/rivet-greeting"
content = templatestring("Hello, $${name}!", { name = "world" })
}

$${ emits literal ${, and %%{ emits literal %{. To write a directive for templatestring, use %%{ if enabled } in the outer HCL string.

Directives and whitespace

FormBehavior
%{ if condition } ... %{ else } ... %{ endif }Boolean condition, optional else, selected branch only.
%{ for value in collection } ... %{ endfor }Iterate a list, set, or map; maps use sorted key order.
%{ for key, value in collection } ... %{ endfor }Map key and value or list index and value bindings.
%{~ ... } and %{ ... ~}Strip adjacent whitespace before or after a marker.
${~ expression ~}Interpolation with adjacent whitespace stripping.

Directives can nest. Loop variables have lexical scope and can shadow outer bindings; the original binding is restored after the loop. Inline HCL templates retain configuration namespaces, while templatefile and templatestring see only their vars map. Directive conditions and collections accept any expression, including operators and for expressions. Sets bind each element as both key and value.

Templates preserve dependency and sensitivity provenance. Rendering is bounded to 16 MiB, depth 128, and 100,000 cumulative loop iterations per compilation. Syntax errors report safe source locations and categories without template text or rendered values. See functions.