Devops

Using Loops in Terraform Templates for Dynamic Infrastructure

April 7, 2026
Published
#Automation#DevOps#Infrastructure as Code#Templates#Terraform

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

JSON
1%{ for server in servers }
2server ${server.name} ${server.ip}:80
3%{ endfor }
4

Terraform code:

TEXT
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}
13

This produces:

TEXT
1server app1 10.0.1.10:80
2server app2 10.0.1.11:80
3

No 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:

TEXT
1%{ for item in collection }
2  ${item}
3%{ endfor }
4

You can also access object fields:

TEXT
1${item.name}
2${item.value}
3

Looping Over Maps

Lists are common, but maps are just as useful.

TEXT
1%{ for key, value in settings }
2${key} = ${value}
3%{ endfor }
4

Terraform input:

TEXT
1settings = {
2  environment = "prod"
3  region      = "us-east-1"
4}
5

Output:

TEXT
1environment = prod
2region = us-east-1
3

This 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.

TEXT
1%{ for server in servers }
2%{ if server.enabled }
3server ${server.name} ${server.ip}
4%{ endif }
5%{ endfor }
6

This 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:

TEXT
1%{ for server in servers ~}
2server ${server.name} ${server.ip}
3%{ endfor ~}
4

This 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.

TEXT
1%{ for group in server_groups }
2[${group.name}]
3%{ for server in group.servers }
4${server}
5%{ endfor }
6
7%{ endfor }
8

This might generate something like:

TEXT
1[web]
2app1
3app2
4
5[db]
6db1
7db2
8

Useful 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:

JSON
1#cloud-config
2packages:
3%{ for pkg in packages }
4  - ${pkg}
5%{ endfor }
6

Terraform:

TEXT
1locals {
2  packages = ["nginx", "git", "docker"]
3}
4
5user_data = templatefile("cloud-init.tpl", {
6  packages = local.packages
7})
8

This 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.

Comments

Leave a comment on this article with your name, email, and message.

Loading comments...

Similar Articles

More posts from the same category you may want to read next.

Share: