Terraform Count [Save your time by using it]

Bits Lovers
Written by Bits Lovers on
Terraform Count [Save your time by using it]

Terraform is a solid tool for describing your infrastructure as code. But if you need to create multiple resources that are nearly identical, copying and pasting the same block gets old fast. Terraform has two ways to handle this: count and for_each. You can also combine these with Dynamic Blocks for more advanced patterns.

A module is a reusable block of Terraform code. Technically, any set of Terraform templates in a folder is a module. I find it helpful to think in terms of “parent” and “child” modules. The parent describes your infrastructure by passing variables down to the children. You can learn more about this in our Terraform overview.

How to use Terraform Count

The count meta-argument lets you create multiple copies of a resource or module. You set it to a whole number (a literal value, a variable, or an expression), and Terraform creates that many instances. Each one is a separate infrastructure object that gets created, updated, or destroyed independently during terraform apply. Count works best when all instances share the same configuration.

Note: count has worked with resources since early Terraform versions, but module support was added in Terraform v0.13.

Basic Syntax

The count meta-argument is built into the Terraform language. You can use it with any resource type and with modules.

Count accepts a whole number and creates that many instances. Each instance has its own infrastructure object, and each is created, updated, or destroyed independently when you run terraform apply.

resource "aws_instance" "web-inst" {
  count = 2 # create two similar EC2 instances
  ami           = "ami-0ed9277fb7eb570c9"
  instance_type = "t3.xlarge"
  tags = {
    Name = "Instance ${count.index}"
    Owner = "Bits Lovers"
  }
}

How to use expressions in the count

The count meta-argument accepts numeric expressions. But unlike most arguments, the count value must be known before Terraform performs any remote operations. That means you cannot reference resource attributes that are only available after the resource is created (like a generated ID from the remote API).

Terraform Count - Real Example 1

Say you need to create Kubernetes secrets across multiple namespaces. Instead of writing one resource block per namespace, you can pass in an array of namespaces and create secrets based on the array length.

resource "kubernetes_secret" "k8_secret" {
  count    = length(var.namespaces)
  provider = kubernetes
  metadata {
    name = "credentials"
    namespace = var.namespaces[count.index]
  }
  data = {
    username = "k8-user"
    password = var.SECRECT
  }
}

Terraform Count - Real Example 2

Another common use case: creating multiple DNS records when all the domain names live in a single list. Using count with length() keeps the code clean.

resource "aws_route53_record" "public-dns" {
  count           = length(var.public_dns)
  zone_id         = var.public_dns_zone_id
  name            = var.public_dns[count.index]
  type            = "CNAME"
  records         = [var.cname]
  ttl             = 90
}

Here we take the public_dns array and use its length to determine how many records to create. Each record uses the corresponding domain name from the array.

Easy, right? Let’s move to the next example.

Terraform Count - Real Example 3

Count is also handy for conditionally creating a resource. By setting count to 0 or 1 based on a variable, you can control whether the resource gets created at all.

In this example, we create a Network Load Balancer only when lb_type equals "network-lb". If the condition is false, count becomes 0 and Terraform skips the resource entirely.

resource "aws_lb" "network-lb" {
  count = var.lb_type == "network-lb" ? 1 : 0
  name = "bitslovers-nlb"
  load_balancer_type = "network"
  subnets         = split(",", var.subnet_ids)
  internal        = var.enable_internal
  enable_deletion_protection = true
}

Note: I removed idle_timeout from this example because that argument only applies to Application Load Balancers. Network Load Balancers use a fixed 350-second idle timeout that AWS controls. If you need longer-lived TCP connections through an NLB, use TCP keepalive on your backend instances.

When you use conditional count like this, watch out for other resources that reference it. Those resources need the same conditional logic, or you need to handle the case where the resource does not exist.

Terraform Count Limitations

Count has two real limitations worth knowing about.

First, you can use count to loop over an entire resource block, but you cannot use count inside a resource to loop over inline blocks. An inline block is a nested configuration within a resource:

resource "type" "name" {
  block_name {
    arg1 = "value1"
    arg2 = "value2"
  }
}

Here block_name is the inline block (for example, tag), and the arguments inside are its parameters. Consider how tags are set in the aws_autoscaling_group resource:

resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.bitslovers.name
  target_group_arns    = [aws_lb_target_group.as_g.arn]
  health_check_type    = "ELB"
  min_size             = var.min_size
  vpc_zone_identifier  = data.aws_subnet_ids.public.ids
  max_size             = var.max_size

  tag {
    key                 = "Owner"
    propagate_at_launch = true
    value               = var.env_name
  }
}

Each tag requires its own inline tag block with key, value, and propagate_at_launch. You might want to let users pass in custom tags dynamically. The old approach was to try to use count inside these inline blocks, but that is not supported. The modern solution is to use Terraform’s dynamic block instead, which handles this exact scenario.

For more on data sources, check our Terraform Data post.

How to Deploy Multiple Resources using For Each

Like count, the for_each meta-argument creates multiple instances of a resource or module. The difference is that instead of specifying a number, you pass in a map or a set of strings. Terraform creates one instance for each element.

Here is how you would create IAM users with for_each:

resource "aws_iam_user" "users" {
  for_each = toset(var.list_users)
  name     = each.value
}

Notice the toset() function. It converts the list into a set, which is required because for_each only accepts sets and maps on resources. As for_each iterates over the set, each username is available in each.value. The username is also in each.key, though you typically use each.key with maps of key-value pairs.

When you use for_each on a resource, the resource becomes a map of instances rather than a list. This is an important distinction from count, which produces a list indexed by position.

Notes on Terraform Count and For Each

For a detailed comparison of when to use each approach, see Terraform for_each vs count.

Before choosing between them, keep these points in mind:

  • You cannot use count and for_each in the same resource or module block.
  • When using for_each, the keys must be known values at plan time. Sensitive values are not allowed as keys.
  • Use count when your resources are nearly identical.
  • Use for_each when resources need different values that cannot be derived from a numeric index.

There is one more thing worth knowing: Terraform also has a for expression, which is different from for_each. A for expression transforms one collection into another. The basic syntax is:

[for <ITEM> in <LIST> : <OUTPUT>]

The LIST is what you iterate over, ITEM is the local variable name for each element, and OUTPUT is an expression that transforms each item. Here is an example that converts a list of names to uppercase:

variable "users" {
  description = "A list of users"
  type        = list(string)
  default     = ["bits", "lovers", ".com"]
}
output "upper_names" {
  value = [for user in var.users : upper(user)]
}

Conclusion

Using count and for_each makes your Terraform code more dynamic and helps you avoid duplicating resource blocks. Both are stable features that have been part of Terraform for years and continue to work the same way in current versions. Once your infrastructure code grows, testing your Terraform configurations becomes essential.

Boost your Terraform Skills:

How and When to use Terraform Modules?

Learn How to use Output on your Terraform and share data across multiple configurations.

Create multiple copies of the same resource using Terraform Count.

Learn How to use Terraform Template, to build dynamic configurations.

Learn the difference between Locals and Variables in Terraform.

Learn what Terraform Data is and how to use it.

Execute Terraform on Gitlab CI.

Use Terraform Null Resource to run local commands and provisioners.

Bits Lovers

Bits Lovers

Professional writer and blogger. Focus on Cloud Computing.

Comments

comments powered by Disqus