How to use Terraform Count Index Meta-Argument? (with Examples)

In Terraform, a resource block is used to create only one infrastructure component (e.g., a virtual machine like an AWS EC2 instance). But what if you need multiple similar infrastructure components (e.g., multiple virtual machines, like a pool of AWS EC2 instances)? Do you need to write out individual resource blocks to create several similar infrastructure objects?

Well, you don’t need to! Terraform has a count meta-argument to help you configure several similar infrastructure objects. Let’s explore what the count meta-argument is and the challenges it comes with. We’ll also discuss how the count.index attribute enables us to reference the current resource instance.

Key Takeaways

  • It is recommended to use the count meta-argument to provision multiple similar infrastructure objects that are almost identical.
  • The count meta-argument can be used in resource, data, or module blocks.
  • The count and for_each meta-arguments cannot be used in the same block, as both are used to create multiple similar infrastructure objects.

What are meta-arguments in Terraform?

To understand what a count meta-argument is used for, let’s start by understanding what meta-arguments are in Terraform. Meta-arguments are special arguments provided by Terraform to be used in the resource, data, or module blocks. The meta-argument supported in each block type varies. For example, the count meta-argument is supported in the resource, data, and module blocks.

Want to learn more about Terraform's resource, data, or module blocks? Check out this video:

The count meta-argument is one of the 2 ways to create multiple instances of an infrastructure resource. The other way is to use the for_each meta_argument. It is commonly used when your instances have some arguments with distinct values.

What is the count meta-argument?

The count meta-argument accepts a whole number. It is used to create multiple instances of an infrastructure object when it is used in a resource or module block. Also, when used in a data block, it fetches multiple instances of an object. Let’s look at some examples that use the count meta-argument.

Try Terraform Count for Each Lab for free

Terraform Count for Each Lab
Terraform Count for Each Lab

count meta-argument examples

As mentioned earlier, the count meta-argument can be used in the resource, data, or module blocks. The examples below demonstrate its usage.

Using count in resource blocks

Let’s start with a simple example below:

# variables.tf
variable "ami" {
  type    = string
  default = "ami-0078ef784b6fa1ba4"
}

variable "instance_type" {
  type    = string
  default = "t2.micro"
}

# main.tf
resource "aws_instance" "sandbox" {
  count         = 3
  ami           = var.ami
  instance_type = var.instance_type
  tags = {
    Name = "sandbox_server"
  }
}

In the code above, 3 AWS EC2 instances (i.e., 3 aws_instance.sandbox infrastructure objects) are provisioned using the count meta-argument. But the issue here is that all EC2 instances will be tagged with the same name (i.e., sandbox_server), which is not what we want.

This is where the count object attribute (i.e., count.index) comes into play. The count.index attribute represents the unique index number of each object created using the count meta-argument. Let’s see how it can be used below:

# variables.tf
variable "ami" {
  type    = string
  default = "ami-0078ef784b6fa1ba4"
}

variable "instance_type" {
  type    = string
  default = "t2.micro"
}

variable "sandboxes" {
  type    = list(string)
  default = ["sandbox_server_one", "sandbox_server_two", "sandbox_server_three"]
}

# main.tf
resource "aws_instance" "sandbox" {
  ami           = var.ami
  instance_type = var.instance_type
  count         = 3
  tags = {
    Name = var.sandboxes[count.index]
  }
}

In the code above, var.sandboxes is a list of strings that represents server names. The count.index attribute represents the current index number of the infrastructure object (i.e., aws_instance.sandbox) being created.

The count.index attribute starts at 0 for the 1st resource instance, then in the next iteration, it is 1 for the 2nd resource instance, and for the last iteration (i.e., since the count is 3), it is index number 2.

Using the count.index like above, is still fragile because each instance is still identified by Terraform using its index and not the string value in the list. For example, the 1st instance will still be referenced as aws_instance.sandbox[0]. This will be discussed further in the “Referencing blocks and block instances” section later in this article.

The count meta-argument also accepts numeric expressions. Let’s see an example below:

# variables.tf
variable "ami" {
  type    = string
  default = "ami-0078ef784b6fa1ba4"
}

variable "instance_type" {
  type    = string
  default = "t2.micro"
}

variable "sandboxes" {
  type    = list(string)
  default = ["sandbox_server_one", "sandbox_server_two", "sandbox_server_three"]
}

# main.tf
resource "aws_instance" "sandbox" {
  ami           = var.ami
  instance_type = var.instance_type
  count         = length(var.sandboxes)
  tags = {
    Name = var.sandboxes[count.index]
  }
}

In the code above, the length function determines the length of the given list (i.e., var.sandboxes). The expression length(var.sandboxes) evaluates to 3, which is used by the count meta-argument to create 3 resource instances.

Using count in data blocks

The count meta-argument can also be used inside data blocks. Let’s see an example below:

# variables.tf
variable "sandboxes" {
  type    = list(string)
  default = ["sandbox_server_one", "sandbox_server_two", "sandbox_server_three"]
}

# main.tf
data "aws_instances" "sandbox_server" {
  count = 2

  filter {
    name   = "tag:Name"
    values = var.sandboxes
  }

  filter {
    name   = "instance-state-name"
    values = ["running"]
  }
}

In the code above, the count meta-argument in the data block is used to fetch only 2 AWS EC2 instances that match the given requirements specified in the nested filter blocks.

Using count in module blocks

The count meta-argument can also be used inside module blocks. Let’s see an example below:

# child module - variables.tf
variable "ami" {
  type    = string
  default = "ami-0078ef784b6fa1ba4"
}

variable "instance_type" {
  type    = string
  default = "t2.small"
}

variable "nameTag" {
  type    = string
  default = "testTag"
}

# child module - web_server.tf
resource "aws_instance" "sandbox" {
  ami           = var.ami
  instance_type = var.instance_type
  tags = {
    Name = var.nameTag
  }
}

# root module - variables.tf
variable "sandboxes" {
  type    = list(string)
  default = ["sandbox_server_one", "sandbox_server_two", "sandbox_server_three"]
}

# root module - main.tf
module "web_servers" {
  source = "./modules/web_servers"

  count         = 3
  instance_type = "t2.micro"
  nameTag       = var.sandboxes[count.index]
}

Terraform Modules are commonly used to hide implementation details of infrastructure objects to be provisioned. You can reuse infrastructure objects by organizing these resources into small and manageable components. A module is just a directory with Terraform configuration files. A root (i.e., parent module) module is a module that calls the child module.

In the example above, the web_servers module is reused to create multiple web server instances using the count meta-argument.

Please note that the example above uses a local Terraform module. You can also use modules in Terraform’s public registry.

Key considerations when working with the count meta-argument

We have seen several examples of how the count meta-argument can be used. Below are some things to keep in mind when working with it.

Referencing blocks and block instances

Blocks that use the count meta-argument (i.e., resource, data, and module blocks) create multiple infrastructure instances. So, how does Terraform refer to each of these instances and also the block itself? Let’s see how.

Referring to the block: use <block_type>.<block_name>. Below are examples of references for block types.

  • Resource block: <resource_type>.<resource_name> e.g. aws_instance.sandbox_server
  • Data block: data.<resource_type>.<resource_name> e.g. data.aws_instances.sandbox_server
  • Module block: module.<module_name> e.g. module.web_servers

Referring to the instance: use <block_type>.<block_name>[<index>]. Below are examples of references for instances that use count

  • Resource block: <resource_type>.<resource_name>[<index>] e.g. aws_instance.sandbox_server[0]. Also, to reference another resource block attribute, you can use <resource_type>.<resource_name>[count.index].attribute e.g aws_instance.sandbox[count.index].arn
  • Data block: data.<resource_type>.<resource_name>[<index>] e.g. data.aws_instances.sandbox_server[0]
  • Module block: module.<module_name>[<index>] e.g. module.web_servers[0]

For a module that creates multiple instances of a specified resource, the instances are prefixed with the module.<module_name>[<index>] in the Terraform UI. Below are trimmed-down versions of the terraform plan output for the module used in the “Using count in module blocks” section:

Expressions with count

As mentioned earlier, the count meta-argument can accept numeric expressions. These numeric expressions must be known before Terraform applies any configuration. We saw an example in the “Using count in resource block” section with the length function, which evaluated to a numeric value.

A numeric expression could also be a conditional expression that evaluates to a number. Let’s see an example below:

# variables.tf
variable "ami" {
  type    = string
  default = "ami-0078ef784b6fa1ba4"
}

variable "instance_type" {
  type = string
  default = "t2.small"
}

# main.tf
resource "aws_instance" "dev" {
  ami           = var.ami
  instance_type = var.instance_type
  count         = var.instance_type == "t2.micro" ? 1 : 0
  tags = {
    Name = "dev_server"
  }
}

From the code above, in the expression var.instance_type == “t2.micro” ? 1 : 0, if the instance_type is not t2.micro, it will evaluate to 0, and no instance will be created.

Limitations with count

Even if the count meta-argument is used to provision multiple infrastructure objects in Terraform; it is recommended to use it when provisioning objects that are almost identical to avoid unexpected infrastructural changes during a modification.

In the example where count is used with a numeric expression in the “Using count in resource block” section, it might be better to use for_each instead.

This is because, with count, if you remove the element at index 0 (i.e., sandbox_server_one) in var.sandboxes, the following happens:

  • The current element at index 1 (i.e., sanbox_server_two) will now become the new element at index 0, i.e., sandbox_server_two is now at index 0
  • The current element at index 2 (i.e., sanbox_server_three) will now be the new element at index 1, i.e., sandbox_server_three is now at index 1
  • There will be no element at index 3, and this index will be destroyed

If you are not bothered about these unintended changes, then count could be a good meta-argument to use in this scenario.

Performance considerations when using count

Below are some best practices to adhere to when working with count:

  • Using count potentially increases the number of API requests made to a Terraform provider for every infrastructure object created. Hence, you need to use count carefully to avoid exceeding API rate limits which can degrade performance.
  • Use count cautiously in data blocks. When you fetch data in large-scale deployments using the data block with the count meta-argument, the result could be quite large. If this happens and the underlying server that Terraform runs on has limited resources (i.e., memory, CPU), it can impact Terraform’s process, which eventually impacts performance.

Test your Terraform mastery with our free Terraform Challenges. They will help in polishing your infrastructure provisioning and management skills!

FAQ

Below are some of the frequently asked questions about the count meta-argument.

When should I use count instead of for_each?

If your resource instances are identical and not impacted by the unintended changes that count causes during modification, then you can use the count meta-argument.

For_each is commonly used when you need to create similar infrastructure objects that have distinct values for their arguments. Also, if you do not want the unexpected changes that come with using count when modifying your infrastructure object.

Can you use both count and for_each meta-arguments in the same block?

You cannot use both in the same resource block because they serve a similar purpose. They both create multiple instances of a resource.

Conclusion

You have learned why Terraform introduced count and can decide when to use the count or for_each meta-argument. Even though count helps you create multiple similar resource instances, you need to know if it is the right meta-argument for your use case. If your infrastructure objects need distinct values that can’t be derived from a whole number, then it’s recommended to use for_each.

Are you looking to polish your Terraform skills in a real-world environment? Enroll in our Terraform for Beginners Course, which covers all of Terraform fundamentals. It includes video lectures, interactive exercises, and hands-on labs to help you internalize concepts and commands.

If you want to gain other Infrastructure as Code (IaC) skills, check out our IaC Learning Path.