Conditionals and Loops
Conditionals
Ternary (inline)
scale {
replicas = var.environment == "production" ? 3 : 1
}
Top-level if blocks
if {
condition = var.environment == "production"
deployment "extra-consumer" {
container "consumer" {
image = image("api")
}
}
}
For Loops
Generate multiple resources from a list variable:
# values.json
{
"tenants": [
{"name": "acme", "domain": "acme.example.com", "plan": "enterprise"},
{"name": "demo", "domain": "demo.example.com", "plan": "starter"}
]
}
for "tenant" "var.tenants" {
deployment "booking" {
name = "booking-${tenant.name}"
container "booking" {
image = image("booking")
env {
TENANT_ID = tenant.name
}
}
scale {
replicas = tenant.plan == "enterprise" ? 3 : 1
}
service {}
ingress {
host = tenant.domain
tls = true
}
}
}
kdef render --dir k8s/ --values values.json
Looping over ingresses
for also accepts top-level ingress blocks, which is the usual way to drive a list of custom domains from data: one service, many registrable domains, the list generated elsewhere and committed.
// sites.json
{
"sites": [
{"name": "acme-nl", "hosts": ["acme.nl", "www.acme.nl"]},
{"name": "example-com", "hosts": ["example.com", "www.example.com"]}
]
}
for "site" "var.sites" {
ingress "cms" {
name = "cms-${site.name}"
service_name = "cms-public-svc"
port = 80
hosts = site.hosts
}
}
The block label is a placeholder here: HCL forbids interpolation in labels, so name supplies the per-iteration resource name. Without it every iteration would resolve to the same name and kdef would reject the collision.
TLS, issuer, class and shared annotations belong in ingress_defaults rather than being repeated per iteration. hosts[0] determines the derived Certificate secret name, so whatever generates the values file should emit a stable first element (order by the canonical host, not by row id) or the secret name churns between renders.
kdef render --dir k8s/ --values sites.json