Hardcoding repetitive configuration is one of the fastest ways to make Terraform feel painful. Whether you're generating config files, cloud-init scripts, or JSON blobs, duplication creeps in quickly.
This is where loops in Terraform templates become incredibly useful. Instead of manually repeating blocks, you can dynamically generate content using iteration directly inside your templates.
Why Template Loops Matter
Terraform itself already supports for_each and count, but those operate at the resource level. When you're generating files or inline configuration, you need something more flexible.
Common scenarios include:
- Building configuration files (NGINX, HAProxy, app configs)
- Generating cloud-init scripts
- Creating JSON/YAML dynamically
- Rendering environment-specific settings
A Quick Example First
Let’s say you want to generate a simple list of backend servers inside a config file.
Template file: servers.tpl
1%{ for server in servers }
2server ${server.name} ${server.ip}:80
3%{ endfor }
4Terraform code:
1locals {
2 servers = [
3 { name = "app1", ip = "10.0.1.10" },
4 { name = "app2", ip = "10.0.1.11" }
5 ]
6}
7
8output "rendered" {
9 value = templatefile("${path.module}/servers.tpl", {
10 servers = local.servers
11 })
12}
13This produces:
1server app1 10.0.1.10:80
2server app2 10.0.1.11:80
3No duplication, no messy string concatenation.
Understanding the Loop Syntax
Terraform templates use a slightly different syntax compared to standard HCL.
- %{ for ... } starts a loop
- %{ endfor } closes it
- ${...} is used for interpolation
Here’s the structure:
1%{ for item in collection }
2 ${item}
3%{ endfor }
4You can also access object fields:
1${item.name}
2${item.value}
3Looping Over Maps
Lists are common, but maps are just as useful.
1%{ for key, value in settings }
2${key} = ${value}
3%{ endfor }
4Terraform input:
1settings = {
2 environment = "prod"
3 region = "us-east-1"
4}
5Output:
1environment = prod
2region = us-east-1
3This pattern is especially handy when generating environment configs or application settings.
Conditional Logic Inside Loops
Here’s where things get interesting. You can combine loops with conditionals.
1%{ for server in servers }
2%{ if server.enabled }
3server ${server.name} ${server.ip}
4%{ endif }
5%{ endfor }
6This ensures only active servers are included.
A common mistake developers make is trying to handle filtering outside the template. While that works, keeping simple logic inside the template often keeps things cleaner and more readable.
Managing Whitespace (Important Gotcha)
Terraform templates can produce unexpected blank lines if you're not careful.
Use the ~ modifier to trim whitespace:
1%{ for server in servers ~}
2server ${server.name} ${server.ip}
3%{ endfor ~}
4This prevents extra newlines from being added.
If your generated files look “off” with spacing, this is usually the reason.
Nested Loops
Yes, you can nest loops. And yes, it can get messy if you're not careful.
1%{ for group in server_groups }
2[${group.name}]
3%{ for server in group.servers }
4${server}
5%{ endfor }
6
7%{ endfor }
8This might generate something like:
1[web]
2app1
3app2
4
5[db]
6db1
7db2
8Useful for structured configs like INI files.
When to Use Template Loops vs HCL Loops
It’s tempting to push everything into templates, but that’s not always the right move.
Use template loops when:
- Generating files (scripts, configs)
- Formatting output matters
- You need fine-grained control over layout
Use HCL loops (for_each, for expressions) when:
- Creating resources
- Transforming data structures
- Keeping logic declarative
A good rule: Templates are for rendering, HCL is for logic.
Real-World Use Case: Cloud-Init Script
Let’s say you’re provisioning instances and want to install multiple packages.
Template:
1#cloud-config
2packages:
3%{ for pkg in packages }
4 - ${pkg}
5%{ endfor }
6Terraform:
1locals {
2 packages = ["nginx", "git", "docker"]
3}
4
5user_data = templatefile("cloud-init.tpl", {
6 packages = local.packages
7})
8This scales cleanly as your package list grows.
Performance and Maintainability
Template loops are lightweight, but readability can degrade quickly if you overuse them.
Keep templates maintainable by:
- Limiting nested logic
- Using meaningful variable names
- Keeping business logic in Terraform, not templates
If a template starts looking like a programming language, it probably needs refactoring.
Wrapping Up
Loops in Terraform templates are a simple feature with a big payoff. They help eliminate repetition, keep configs clean, and make your infrastructure more adaptable.
Once you start using them for things like config generation or cloud-init scripts, it’s hard to go back to static files.
The key is balance: use loops to simplify output, not to bury logic where it becomes hard to debug.