Terraform Associate Certification: HashiCorp Certified

Read generate and modify configuration

Count and for each

In this guide, we'll explore how to use Terraform's meta-arguments—count and for_each—to efficiently create multiple instances of a resource using the same configuration block. Understanding these options will help you manage resource deployments more dynamically and reliably.

Using Count

The count meta-argument allows you to create multiple copies of a resource. In the example below, we launch three EC2 instances by setting count to 3:

resource "aws_instance" "web" {
  ami           = var.ami
  instance_type = var.instance_type
  count         = 3
}

variable "ami" {
  default = "ami-06178cf087598769c"
}

variable "instance_type" {
  default = "m5.large"
}

After running terraform apply, Terraform records these instances in the state file as a list. This means that each resource is identified by its index, for example:

  • aws_instance.web[0]
  • aws_instance.web[1]
  • aws_instance.web[2]

Dynamically Setting Count Using a List Variable

To streamline your configuration, you can use a list variable instead of a hardcoded count. The following configuration uses the length function to determine the number of instances based on the number of elements in the webservers list:

resource "aws_instance" "web" {
  ami           = var.ami
  instance_type = var.instance_type
  count         = length(var.webservers)
  tags = {
    Name = var.webservers[count.index]
  }
}

variable "ami" {
  default = "ami-06178cf087598769c"
}

variable "instance_type" {
  default = "m5.large"
}

variable "webservers" {
  type    = list(string)
  default = ["web1", "web2", "web3"]
}

In this configuration, Terraform creates as many instances as there are elements in the webservers list, with each instance tagged according to its corresponding name.

Tip

Using a dynamic list makes your Terraform configuration more flexible and easier to maintain when scaling resources.

Potential Drawback of Using Count

One important limitation of the count meta-argument is that it organizes resources based on list indices. If the order of the elements changes or an element is removed, Terraform may update the wrong resource or destroy the unintended instance.

For example, consider the updated configuration when "web1" is removed from the list:

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

# variables.tf
variable "ami" {
  default = "ami-06178cf087598769c"
}

variable "instance_type" {
  default = "m5.large"
}

variable "webservers" {
  type    = list(string)
  default = ["web2", "web3"]
}

Running terraform plan may generate an execution plan like this:

$ terraform plan
...
Terraform will perform the following actions:

# aws_instance.web[0] will be updated in-place
~ resource "aws_instance" "web" {
    ami = "ami-06178cf087598769c"
    ...
    tags = {
        ~ "Name" = "web1" -> "web2"
    }
}

# aws_instance.web[1] will be updated in-place
~ resource "aws_instance" "web" {
    ami = "ami-06178cf087598769c"
    ...
    tags = {
        ~ "Name" = "web2" -> "web3"
    }
}

# aws_instance.web[2] will be destroyed
- resource "aws_instance" "web" {
    ...
}

Plan: 0 to add, 2 to change, 1 to destroy.

Warning

Since resources are managed as a list when using count, removing or reordering items can lead to unintended updates or inadvertent deletion of resources. Always verify your plan before applying such changes.

Using For_Each

The for_each meta-argument offers an alternative approach by creating a resource for each element in a set or map. Unlike count, for_each stores resources in a map keyed by each element’s value, eliminating issues caused by index reordering.

Consider the following configuration that uses for_each to loop through the webservers variable:

resource "aws_instance" "web" {
  ami           = var.ami
  instance_type = var.instance_type
  for_each      = var.webservers
  tags = {
    Name = each.value
  }
}

variable "ami" {
  default = "ami-06178cf087598769c"
}

variable "instance_type" {
  default = "m5.large"
}

variable "webservers" {
  type    = set(string)
  default = ["web1", "web2", "web3"]
}

Key advantages of using for_each:

  • The webservers variable is defined as a set (or map) to ensure unique keys.
  • Each resource is identified by its key, such as aws_instance.web["web1"].
  • Removing an element (like "web1") only destroys that specific resource without affecting the others.

After applying this configuration, running terraform state list displays resources as:

$ terraform state list
aws_instance.web["web1"]
aws_instance.web["web2"]
aws_instance.web["web3"]

This predictable mapping means that modifying the set—such as removing "web1"—only affects the corresponding resource.

Conclusion

In this article, we explored two strategies for provisioning multiple instances in Terraform: using count and using for_each. While count is straightforward, it can introduce complications when handling dynamic lists due to index-based identification. In contrast, for_each facilitates more predictable resource management by leveraging set or map keys.

By understanding these distinctions, you can choose the most appropriate approach for your Terraform configurations and avoid unintended resource modifications. Stay tuned for our next article, where we'll dive deeper into advanced Terraform concepts and best practices.

For more detailed Terraform documentation and best practices, visit the Terraform Registry or HashiCorp's documentation.

Watch Video

Watch video content

Previous
Terraform workspace