Terraform: Introduction to count

Using count with resources in Terraform

In the course of using Terraform, you will undoubtedly encounter the need to build more than one of something. Be it an instance group, cloud function, or VPC, Terraform has a couple of options for specifying multiple instances of a resource: count and for_each.

This post will discuss the use of count.

For information on for_each, see this post.

Note: count and for_each are only available when using Terraform 0.12 or higher.

Creating Multiple Resources

To use count with a resource, simply add the count argument and assign it an integer value. All Terraform resources support this. For demo purposes, we will use the null_resource resource as this doesn’t create anything - very useful for testing and debugging!

We first create a single resource and compare it to the same resource created with count:

# Create a single "null_resource" resource
resource "null_resource" "my_single_resource" {}

# Create two "null_resource" resources
resource "null_resource" "my_multi_resource" {
    count = 2
}

# Output the map that makes up the single "my_single_resource" resource
output "my_single_resource" {
    value = null_resource.my_single_resource
}

# Output the list of maps that make up the "my_multi_resource" resource
output "my_multi_resource" {
    value = null_resource.my_multi_resource
}
# terraform apply -auto-approve
null_resource.my_multi_resource[0]: Creating...
null_resource.my_single_resource: Creating...
null_resource.my_multi_resource[1]: Creating...
null_resource.my_single_resource: Creation complete after 0s [id=7743138044345878967]
null_resource.my_multi_resource[0]: Creation complete after 0s [id=7941926376440286708]
null_resource.my_multi_resource[1]: Creation complete after 0s [id=919527870867381583]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

my_multi_resource = [
  {
    "id" = "7941926376440286708"
  },
  {
    "id" = "919527870867381583"
  },
]
my_single_resource = {
  "id" = "7743138044345878967"
}


A single resource is represented with a map; adding count creates a list of maps. Each map contains the exported attributes for a single instance of the resource. In this case, only one attribute is exported: id. We can retrieve the null_resource list of maps by specifying the name of the created resource.

Static Resource Lookup

If you want to retrieve the list of maps that represents the resource, you can simply refer to the resource name as shown above. If you want to retrieve an item in the list, you can access it by using its index number. Each list in Terraform starts with index 0. If you want to retrieve a list of a specific attribute, you can use the * designation.

resource "null_resource" "my_resource" {
    count = 2
}

# Output the list of maps that make up the "my_resource" resource
output "my_resource" {
    value = null_resource.my_resource
}

# Output a list of strings of all the "my_resource" ids
output "my_resource_ids" {
    value = null_resource.my_resource[*].id
}

# Output the first resource map in the list
output "my_first_resource" {
    value = null_resource.my_resource[0]
}

# Output the second resource map in the list
output "my_second_resource" {
    value = null_resource.my_resource[1]
}

# Output the id attribute of the first resource
output "my_first_resource_id" {
    value = null_resource.my_resource[0].id
}

# Output the id attribute of the second resource
output "my_second_resource_id" {
    value = null_resource.my_resource[1].id
}
# terraform apply -auto-approve
null_resource.my_resource[1]: Creating...
null_resource.my_resource[0]: Creating...
null_resource.my_resource[1]: Creation complete after 0s [id=7184041985664490631]
null_resource.my_resource[0]: Creation complete after 0s [id=5361751923704334335]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

my_first_resource = {
  "id" = "5361751923704334335"
}
my_first_resource_id = 5361751923704334335
my_resource = [
  {
    "id" = "5361751923704334335"
  },
  {
    "id" = "7184041985664490631"
  },
]
my_resource_ids = [
  "5361751923704334335",
  "7184041985664490631",
]
my_second_resource = {
  "id" = "7184041985664490631"
}
my_second_resource_id = 7184041985664490631

Dynamic Resource Lookup

The above example used a hard-coded count number and a static lookup for each item by specifying an index number:

null_resource.my_resource[0]
null_resource.my_resource[0].id


When building multiple resources with count, the current index can be retrieved with count.index. This functionality, combined with the for and length expressions, can be used to loop through lists and retrieve items.

locals {
  my_list = ["a", "b", "c"]
}

# We create as many resources as there are items in my_list
# and add a trigger that is tied to the corresponding item in the list
resource "null_resource" "my_resource" {
    count = length(local.my_list)

    triggers = {
        name = local.my_list[count.index]
    }
}

# Output a list of maps that make up the "my_resource" resource
output "individual_my_resource" {
    value = [
        for resource in null_resource.my_resource:
            resource
    ]
}

# Output a list of strings of all the "my_resource" ids
output "my_resource_ids" {
    value = [
        for resource in null_resource.my_resource:
            resource.id
    ]
}
# terraform apply -auto-approve
null_resource.my_resource[0]: Creating...
null_resource.my_resource[1]: Creating...
null_resource.my_resource[2]: Creating...
null_resource.my_resource[0]: Creation complete after 0s [id=2668212934547241509]
null_resource.my_resource[2]: Creation complete after 0s [id=241436459065527357]
null_resource.my_resource[1]: Creation complete after 0s [id=9122084521974301316]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

individual_my_resource = [
  {
    "id" = "2668212934547241509"
    "triggers" = {
      "name" = "a"
    }
  },
  {
    "id" = "9122084521974301316"
    "triggers" = {
      "name" = "b"
    }
  },
  {
    "id" = "241436459065527357"
    "triggers" = {
      "name" = "c"
    }
  },
]
my_resource_ids = [
  "2668212934547241509",
  "9122084521974301316",
  "241436459065527357",
]

Considerations

Because count creates a list of resources (and is typically used to look up items in lists), the usual considerations associated with lists apply:

  • Lists are ordered and indexed
    • The item in the nth position will always be in the nth position
    • Each item can be looked up by its position (i.e. its index)
locals {
  my_list = ["one", "two", "three", "four"]
}

# The values of these outputs will never change so long as the list is not changed
output "first" {
    value = local.my_list[0]
}

output "second" {
    value = local.my_list[1]
}

output "third" {
    value = local.my_list[2]
}

output "fourth" {
    value = local.my_list[3]
}
# terraform apply -auto-approve

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

first = one
fourth = four
second = two
third = three

  • Items in a list can repeat, so lists are not a good way to identify unique resources. In this example, each item’s index is unique, but you would not be able to tell them apart by looking at their triggers.name:
locals {
  my_list = ["a", "b", "a", "b"]
}

resource "null_resource" "my_resource" {
    count = length(local.my_list)

    triggers = {
        name = local.my_list[count.index]
    }
}

# Output a list of strings of all the "my_resource" trigger names
output "my_resource_trigger_names" {
    value = [
        for resource in null_resource.my_resource:
            resource.triggers.name
    ]
}
# terraform apply -auto-approve
null_resource.my_resource[1]: Creating...
null_resource.my_resource[0]: Creating...
null_resource.my_resource[3]: Creating...
null_resource.my_resource[2]: Creating...
null_resource.my_resource[3]: Creation complete after 0s [id=1846209597499973472]
null_resource.my_resource[1]: Creation complete after 0s [id=1443643817645220217]
null_resource.my_resource[0]: Creation complete after 0s [id=2453973180062407248]
null_resource.my_resource[2]: Creation complete after 0s [id=3284318591447876783]

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

my_resource_trigger_names = [
  "a",
  "b",
  "a",
  "b",
]

  • Removing an item from a list will change the position (index) of any items after it.
    • If the index number for a terraform resource changes, that resource will be DESTROYED and recreated.
    • In this example, my_list has item b removed. This changes c’s index from 2 to 1, causing the initial resource tied to this item to be destroyed and replaced with a new one:
# Initial list
locals {
  my_list = ["a", "b", "c"]
}

resource "null_resource" "my_resource" {
    count = length(local.my_list)

    triggers = {
        name = local.my_list[count.index]
    }
}

# Output the list of maps that make up the "my_resource" resource
output "my_resource" {
    value = null_resource.my_resource
}
# terraform apply -auto-approve
null_resource.my_resource[1]: Creating...
null_resource.my_resource[2]: Creating...
null_resource.my_resource[0]: Creating...
null_resource.my_resource[0]: Creation complete after 0s [id=7853912210842706217]
null_resource.my_resource[1]: Creation complete after 0s [id=7506025364667954584]
null_resource.my_resource[2]: Creation complete after 0s [id=4193769900642502772]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

my_resource = [
  {
    "id" = "7853912210842706217"
    "triggers" = {
      "name" = "a"
    }
  },
  {
    "id" = "7506025364667954584"
    "triggers" = {
      "name" = "b"
    }
  },
  {
    "id" = "4193769900642502772"
    "triggers" = {
      "name" = "c"
    }
  },
]

   

# Updated list with "b" removed
locals {
  my_list = ["a", "c"]
}

resource "null_resource" "my_resource" {
    count = length(local.my_list)

    triggers = {
        name = local.my_list[count.index]
    }
}

# Output the list of maps that make up the "my_resource" resource
output "my_resource" {
    value = null_resource.my_resource
}
# terraform apply -auto-approve
null_resource.my_resource[1]: Refreshing state... [id=7506025364667954584]
null_resource.my_resource[2]: Refreshing state... [id=4193769900642502772]
null_resource.my_resource[0]: Refreshing state... [id=7853912210842706217]
null_resource.my_resource[2]: Destroying... [id=4193769900642502772]
null_resource.my_resource[1]: Destroying... [id=7506025364667954584]
null_resource.my_resource[2]: Destruction complete after 0s
null_resource.my_resource[1]: Destruction complete after 0s
null_resource.my_resource[1]: Creating...
null_resource.my_resource[1]: Creation complete after 0s [id=628982781686439593]

Apply complete! Resources: 1 added, 0 changed, 2 destroyed.

Outputs:

my_resource = [
  {
    "id" = "7853912210842706217"
    "triggers" = {
      "name" = "a"
    }
  },
  {
    "id" = "628982781686439593"
    "triggers" = {
      "name" = "c"
    }
  },
]


If you wish to create multiple resources that have a unique designation and are not affected by the removal of items, it is recommended that you use for_each instead of count.

For information on for_each, see this post.