using "count" for cleaner terraform
Share on facebook
Share on twitter
Share on linkedin

Using “count” for Cleaner Terraform


Terraform is a fantastic tool for deploying resources. You type a resource in your script, run terraform apply and voila! You have infrastructure! But, the idea of having to add a resource block for every single resource can be daunting and really clutter your scripts. What if there was a way to multiply similar resources using one parameter? By golly, there is! That parameter is “count” and we’re going to cover it in this guide! For this guide to be useful, it is expected that you already know how to write Terraform scripts and deploy Terraform resources. This is not an “Intro to Terraform” guide. If you need an introduction to Terraform, check out our Managing Applications and Infrastructure with Terraform course! Let’s use a fairly simple example for this guide, so we can focus more on the theory and less on overwhelming syntax. Let’s say I want to deploy two S3 buckets:

#---- ------provider "aws" {  region = "us-east-1"}# Create a random ID for bucket 1resource "random_id" "tf_bucket1_id" {  byte_length = 2}# Create a random ID for bucket 2resource "random_id" "tf_bucket1_id" {  byte_length = 2}# Create bucket 1resource "aws_s3_bucket" "tf_bucket1" {  bucket        = "terraform-bucket-${random_id.tf_bucket1_id.dec}"  acl           = "private"  force_destroy = true  tags {    Name = "tf_bucket1"  }}# Create bucket 2resource "aws_s3_bucket" "tf_bucket2" {  bucket        = "terraform-bucket-${random_id.tf_bucket2_id.dec}"  acl           = "private"  force_destroy = true  tags {    Name = "tf_bucket2"  }}

Alright, that is just ugly. It takes the idea of “Don’t Repeat Yourself” and slaps it right in the face! Let’s first talk about what’s going on, then we’ll refactor. First, we deploy two “random_id” resources. These will append a random string of digits to each bucket in order to ensure entropy. All S3 buckets in AWS must have names that are globally unique. In other words, you don’t want to risk your bucket having the same name as any other S3 bucket in the entire world. If the name matches, you’re going to have a failed deployment! Failed deployments are not good for automation, so we have created two random IDs to append to our buckets to help ensure that doesn’t happen.

# Create a random ID for bucket 1resource "random_id" "tf_bucket1_id" {  byte_length = 2}# Create a random ID for bucket 2resource "random_id" "tf_bucket1_id" {  byte_length = 2}

Next, we deploy two S3 buckets. These buckets are each named “terraform-bucket-<ID>” where the “<ID>” is the 2 byte, or 5 digits, decimal string appended to the end of each S3 bucket. So, if you run a terraform apply, you should see both buckets created. All is well and good… or is it? No, I’m just kidding! It’s good, but it certainly could be better if there weren’t so many resources! Let’s take a look at refactoring our app for more readability and less room for error. Let’s use count! "aws" {  region = "us-east-1"}# Create a random ID resource "random_id" "tf_bucket_id" {  byte_length = 2  count = 2}# Create the bucketresource "aws_s3_bucket" "tf_code" {  count         = 2  bucket        = "terraform-bucket-${random_id.tf_bucket_id.*.dec[count.index]}"  acl           = "private"  force_destroy = true  tags {    Name = "tf_bucket"  }}

Wow! Doesn’t that look a lot cleaner? Of course, it does! We’ve removed roughly 15 lines of code! That’s not much now, but in a script with many more resources, it adds up quickly! Let’s go over exactly what is going on here:

# Create a random ID resource "random_id" "tf_bucket_id" {  byte_length = 2  count = 2}

In the first block, we have removed one of the random IDs and we have added the “count” parameter and set it equal to “2”. That’s all we had to do! We didn’t have to create multiple IDs or perform any other steps. Adding count = 2 handles everything needed to ensure we have two random IDs created for use by our S3 bucket resources. Let’s take a look at those now:

# Create the bucketresource "aws_s3_bucket" "tf_code" {  count         = 2  bucket        = "terraform-bucket-${random_id.tf_bucket_id.*.dec[count.index]}"  acl           = "private"  force_destroy = true  tags {    Name = "tf_bucket"  }}

That’s definitely a lot more succinct! There is one slightly complicated bit, and that’s the bucket parameter because it is a little longer. Although it’s longer, it isn’t that complicated. Let’s break it down:First, you have terraform-bucket-random_id.tf_bucket_id just as before. But, then, instead of just going straight to the .dec, we have added an asterisk (*) between it and the resource ID. That asterisk lets Terraform know that we are going to be iterating through multiple items. After that, we add our dec, then we get to the next weird part. We have to choose which random ID to use. I’m assuming you have some familiarity with programming, so this should be fairly straightforward. The [count.index] is just referencing the index of the current iteration of count. When Terraform uses “count”, it iterates over the resources the specified number of times and assigns an index each time. Just like indexes are supposed to work, these indexes start with “0”. So, the first iteration would read:


and the second iteration would read:


Instead of specifying these statically, we just use [count.index] to dynamically specify the current iteration of the script! Neat! Perform a terraform apply and see how it goes! I bet it went well! Before we close, let’s go over one more item that you may want to include: the output. What we want to do is output a comma-delimited list of bucket names. Let’s add an output to our script and see how it comes out:

output "bucketname" {  value = "${join(", ", aws_s3_bucket.tf_code.*.id)}"}

That looks simple enough. Let’s take a look at that join function and pick it apart a little. The first thing, other than the join that you may notice, is the absence of [count.index]. We don’t need [count.index] here since we want to export both values. So, what we have is perfectly fine. Next, let’s look at the join function:

"${join(", ", aws_s3_bucket.tf_code.*.id)}"

First, we have “join” and then our delimiter. The delimiter is a comma in this case and that is separated by quotes. Then, we have another comma and next is the list we wish to delimit. We could use a colon, semicolon, or any other delimiter we want! Perform a terraform destroy and another terraform apply to see it in action again and to see your brand new outputs! Did everything work? If so, great! You’re on your way to “DRY” er, cleaner, and more maintainable deployments! The full script is included below: "aws" {  region = "us-east-1"}# Create a random ID resource "random_id" "tf_bucket_id" {  byte_length = 2  count = 2}# Create the bucketresource "aws_s3_bucket" "tf_code" {  count         = 2  bucket        = "terraform-bucket-${random_id.tf_bucket_id.*.dec[count.index]}"  acl           = "private"  force_destroy = true  tags {    Name = "tf_bucket"  }}output "bucketname" {  value = "${join(", ", aws_s3_bucket.tf_code.*.id)}"}

Don’t forget to follow our other Terraform courses on Linux Academy, and “Terraform Apply yourself!”


Get more insights, news, and assorted awesomeness around all things cloud learning.

Sign In
Welcome Back!

Psst…this one if you’ve been moved to ACG!

Get Started
Who’s going to be learning?