Using for loop in template with count


I have a simple variable:

variable "ipv4_addresses" {
  type    = list(object({ eth0 = string, eth1 = string }))
  default  = null

And I’m trying to create a template object with count like following example:


vm_count = 2
ipv4_addresses = [{ eth0 =, eth1 = }, { eth0 =, eth1 = }]

TF code:

data "template_file" "metadata" {
  count    = var.vm_count
  template = <<EOF
%{ for i in var.ipv4_addresses[count.index] ~}
interface 1 = {i.eth0}
interface 2 = {i.eth1}
%{ endfor ~}

And I don’t understand how to access to each object value eth0 and eth1 in for loop depend on count? %{ for i in var.ipv4_addresses[count.index] ~} does not work…

1 Like

Hi @igor-nikiforov,

Before getting into the specific syntax, I want to note that the way you’re using template_file here is redundant: the expression you assigned to template is itself a string template expression that’ll already be rendered by the time the value gets passed into the template_file data source, and so there’s nothing left for the data source to render at that point. template_file is a deprecated data source which remains only to support folks still using Terraform v0.11 or earlier, which didn’t yet have the full template syntax we have in modern Terraform.

Given that, I’d start by replacing your data "template_file" block with a local value declaration, like this:

locals {
  metadata = [
    for i in range(var.vm_count) : <<EOF
interface 1 = ${var.ipv4_addresses[i].eth0}
interface 2 = ${var.ipv4_addresses[i].eth1}

In the above I used a for expression with the range function to get the same effect as the count = var.vm_count in your example, and so the result of this would be a list of strings where each string is the result of rendering the template for one of your VMs.

If I’ve understood your goal correctly, I think the above should work allowing you to use local.metadata to access the rendered templates.

However, having multiple separate variables that all together have to agree on how many elements there are and correlate by index tends to be quite hard to maintain from the caller’s perspective, because they need to be careful to keep the indexes correlated, so I’d suggest thinking about a different organization where everything about your VMs is together in a single variable. You only showed the count and the IP addresses in this example and so I’ll focus on those for the following example, but hopefully you can see how this could extend to capture other important attributes for each virtual machine, if any:

variable "vms" {
  type = list(object({
    interfaces = object({
      eth0 = string
      eth1 = string

locals {
  vm_metadata = [
    for vm in var.vms : <<EOF
interface 1 = {vm.interfaces.eth0}
interface 2 = {vm.interfaces.eth1}

# You didn't mention which cloud provider you're
# using, so I've just used AWS here as an
# example.
resource "aws_instance" "example" {
  # The length of var.vms implies the desired number of VMs
  count = length(var.vms)

  # ...

  # Select the corresponding element from local.vm_metadata
  # to use as the user_data.
  user_data = local.vm_metadata[count.index]

An advantage of this structure is that a user of your module can just add new elements to the vms variable to both increase the number of VMs and define their network interface settings at the same time, and so there isn’t any risk of forgetting to update one of them or declaring the elements of one of the lists in the wrong order.

(I used count here because that’s how you structured your example, but note that this is appropriate only for situations where all of your VMs are interchangeable in the sense that if you needed to scale down the number then it wouldn’t matter which one is to be destroyed. If the instances are not interchangeable – that is, if you need to control the lifecycle of each one specifically – then it’d be more appropriate to use for_each, but that’s getting quite beyond the scope of what you asked so I won’t get into that here.)


Hi @apparentlymart,

Thank you for very detailed explanation!