When we talk about or study Infrastructure as Code (IaC) concepts, Terraform comes to our minds first. Terraform is a popular Infrastructure as a Code tool among users due to its simplicity and wider community support.

Suppose you want to provision and manage your IT infrastructure resources through Terraform. In that case, you only have to write a simple script using HCL (HashiCorp Configuration Language) describing what needs to be done.

The Count meta argument takes a significant place among the features Terraform provides. This article will dive deeply into the Count meta-argument feature with some examples.

Before moving to the Count meta argument, let’s look at the meta arguments in Terraform.

Meta-arguments in Terraform.

In Terraform, meta arguments are a special version of arguments that can be used with any type of resource block so the users can easily change the resource’s behavior.

Terraform supports several meta-arguments, and they are listed below.

  • count: Creates multiple, near-identical resources based on a numerical value.
  • for_each: Creates multiple, non-identical resources based on a numerical value.
  • depends_on: Defines the dependencies between two or more resources.
  • provider: Defines the provider configuration for a specified resource (Useful when there are multiple providers of the same type).
  • provisioner: Specifies the actions that need to be performed after a resource is created or updated.

As you can see, count and for_each meta arguments perform the same task but with a small change.

Firstly, let’s go through a complete overview of the count meta argument.

count meta-argument

In Terraform, the count meta argument simplifies creating multiple resources from a specified resource type. But how does it simplify?

The traditional method of creating multiple instances is repeatedly writing the same resource block. It gets complicated when we have to create many of the same instances.

But with the count meta argument, we can avoid repeating the same resource block. 

  • Firstly, we specify the count argument within the block (Can be used with resource and module blocks).
  • Then, we must give a whole number as the argument’s value. This value should represent the number of resources we want to create.

Once we perform the Terraform workflow (terraform init, plan and apply), it will create the specified number of instances. We can verify the process with the output of the terraform plan command.

Let’s take a simple example where we want to construct four Amazon EC2 instances.

Traditional method: Repeating the same resource block four times.

resource "aws_instance" "webserver1" {

   ami           = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = "web-server"

   }

}

resource "aws_instance" "webserver2" {

   ami           = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = "web-server"

   }

}

resource "aws_instance" "webserver3" {

   ami           = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = "web-server"

   }

}

resource "aws_instance" "webserver4" {

   ami           = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = "web-server"

   }

}

With count meta argument: Using count meta argument with the value ‘4’.

resource "aws_instance" "webserver" {

   count = 4

   ami   = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = "web-server"

   }   

}

When we run the terraform plan command, we can see the output like the one below.

+ resource "aws_instance" "webserver" {

    + ami                                  = "ami-0287a05f0ef0e9d9a"

    + arn                                  = (known after apply)

    + associate_public_ip_address          = (known after apply)

    + availability_zone                    = (known after apply)

+ resource "aws_instance" "webserver" {

    + ami                                  = "ami-0287a05f0ef0e9d9a"

    + arn                                  = (known after apply)

    + associate_public_ip_address          = (known after apply)

    + availability_zone                    = (known after apply)

+ resource "aws_instance" "webserver" {

    + ami                                  = "ami-0287a05f0ef0e9d9a"

    + arn                                  = (known after apply)

    + associate_public_ip_address          = (known after apply)

    + availability_zone                    = (known after apply)

+ resource "aws_instance" "webserver" {

    + ami                                  = "ami-0287a05f0ef0e9d9a"

    + arn                                  = (known after apply)

    + associate_public_ip_address          = (known after apply)

    + availability_zone                    = (known after apply)

As shown above, we can easily provision multiple resources without repeating the codes.

But there is a small issue. All the instances get the same name: “webserver”. This is where count objects come into the picture.

count Object in Terraform

When we use the count meta argument within a resource block, it comes with the count object. The count object has only one attribute called “index”. This index attribute is a sequential number for the resources created with the count meta argument, which starts from 0.

This index can generate unique names for the instances when creating them.

resource "aws_instance" "webserver" {

   count = 4

   ami   = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = "web-server-${count.index}"

   }   

}

Earlier, we had the same name for our four web servers. Here, we have used the count object with the attribute index to get the unique names for each web server.

See the below output.

resource "aws_instance" "webserver" {

       id                                   = "i-cc7cb201506a600d1"

       tags                                 = {

           "Name" = "web-server" -> "web-server-0"

      }

resource "aws_instance" "webserver" {

       id                                   = "i-59cb17cd1d10b24b0"

       tags                                 = {

           "Name" = "web-server" -> "web-server-1"

      }

resource "aws_instance" "webserver" {

       id                                   = "i-a46a9c422020226e5"

       tags                                 = {

           "Name" = "web-server" -> "web-server-2"

       }

resource "aws_instance" "webserver" {

       id                                   = "i-220202a46a9c426e5"

       tags                                 = {

           "Name" = "web-server" -> "web-server-3"

       }
Plan: 0 to add, 4 to change, 0 to destroy.

As shown in the output, we can have unique names for our web servers.

Refer to external resource configurations using the count object.

Instead of using the index in expressions, we can refer to an external configuration for the resources we create.

Imagine we have a local outside the resource block that includes names for our web servers as a list (webserverNames). See the code chunk below.

locals {

   webserverNames=["web-server-1", "web-server-2", "web-server-3", "web-server-4"]

}

We can access those elements using the index by changing the name expression as below.

locals {

   webserverNames=["web-server-1", "web-server-2", "web-server-3", "web-server-4"]

}

resource "aws_instance" "webserver" {

   count = 4

   ami   = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = local.webserverNames[count.index]

   }   

}

This is another useful way to use the index for assigning unique names to our web servers.

But, when the number of elements in the webserverNames list increases, we have to set the value for the count manually. This can be complex when there are more instances to create.

As a solution, we can conditionally set the value of the count argument.

Use the count meta argument with conditional statements.

locals {

   webserverNames=["web-server-1", "web-server-2", "web-server-3", "web-server-4"]

}

resource "aws_instance" "webserver" {

   count = length(local.webserverNames)

   ami   = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

   tags = {

       name = local.webserverNames[count.index]

   }   

}

Now, the count argument will use the length of the webserverNames list as the number of instances to be created.

This allows users to have a more flexible experience using the count meta argument for better provision of infrastructure resources.

Let’s briefly explore the difference between count and for_each meta arguments.

count vs for_each

As we discussed earlier, we use count meta-arguments to construct near-identical resources.

Note: Identical / Near-Identical resources are known as those of a particular resource type, including the same settings.

For example, think of creating 10 Amazon EC2 instances with the same configuration settings (same AMIs, instance types, etc).

resource "aws_instance" "web-server" {

   count = 10

   ami   = "ami-0287a05f0ef0e9d9a"

   instance_type = "t3.nano"

}

But for_each meta arguments are used to create non-identical resources.

Note: Non-identical resources are those created from a specified resource type with separate configuration settings.

For example, think of creating 10 Amazon EC2 instances with different configuration settings, such as different AMIs, instance types, etc.

resource "aws_instance" "web-server" {

   for_each = {

       webserver1 = "ami-0287a05f0ef0e9d9a"

       webserver2 = "ami-0e9d9a0287a05f0ef"

   }

   ami = each.value

   instance_type = "t3.micro"

}

So, based on the nature of the use cases, we need to decide whether to use “count” or “for_each” arguments.

Benefits of using the count meta argument.

When we discuss the pros of the count meta argument, the main advantage it gives us is scalability. As we saw earlier, we can easily scale up or down as needed without repeating or deleting the same resource blocks.

Another benefit is the count meta argument simplifies our code. We can create multiple instances with two or three code lines without writing too much code. This helps to have a clean, simple and maintainable code.

Also, we can ensure that our code follows terraform and coding best practices since count argument uses loops and iterations instead of hardcoding them.

Conclusion

This writing deepens into the world of the count meta argument in Terraform. When creating multiple instances with the same configuration, we can complete the task with the count argument without repeating the same resource blocks. It enhances the code readability, maintainability and simplicity. Since it emphasizes scalability, it helps us to save valuable time, and we can increase or decrease the instances based on the demand.
count meta argument is one of the perfect features we can use to have a great experience provisioning and managing infrastructure resources through Terraform