Skip to content
Welcome To Charanjit Cheema Blog

Welcome To Charanjit Cheema Blog

An Open Source and Cloud Blog

Menu
  • Home
  • About Me!
  • Way to my Technical Blog
  • Contact me
  • Privacy Policy
Menu

Iterating Cloud Resource Provisioning Using Terraform Count and For_Each Meta-Arguments

Posted on January 27, 2025January 27, 2025 by Charanjit Singh

I have sometimes seen some people feel confused in choosing between count and for_each in Terraform. Both constructs help to dynamically create multiple resources, yet their use cases and behavior differ significantly. Let me break it down in simple terms and demonstrate their differences with an example: provisioning AWS Security Groups.

Note: Since creating Security Groups in AWS is free, I am using an example of provisioning AWS Security Groups to illustrate these constructs.

 

Understanding the Basics: count and for_each

Count

Count is the simpler of the two constructs. It creates resources based on a numeric value. It is like you’re asking Terraform, “Make X copies of this resource,” and that’s exactly what it does. Every resource created with count is identical, except for slight variations you introduce using count.index, which is a zero-based index.

For_Each

For_each, on the other hand, is designed for scenarios where you need to create resources with distinct configurations. It iterates over a collection (list, set, or map), and for each element in the collection, it creates a unique resource. Unlike count, the resulting resources are associated with the elements of the collection, allowing for easier referencing and modification later.

 

The Key Differences

Aspect count for_each
Input Requirements Works with a single numeric value. Works with collections like lists or maps.
Best Use Case Best for identical resources or conditional creation of resources. Ideal when resources require unique configurations.
Referencing Resources Resources are indexed using count.index. Resources are referenced using the collection’s keys.
Flexibility Simpler and best suited for repetitive tasks. Offers greater customization and handles complex resource configurations effectively.

 

 

Security Group Provisioning with count

Let’s start with a simple example of creating Security Groups using count.

variable “number_of_sgs” {

default = 2

}

 

resource “aws_security_group” “web_sg” {

count = var.number_of_sgs

 

name        = “web-sg-${count.index + 1}”

description = “Security Group for web instances ${count.index + 1}”

 

ingress {

from_port   = 80

to_port     = 80

protocol    = “tcp”

cidr_blocks = [“0.0.0.0/0”]

}

 

egress {

from_port   = 0

to_port     = 0

protocol    = “-1”

cidr_blocks = [“0.0.0.0/0”]

}

}

In this example, Terraform creates two Security Groups, named web-sg-1 and web-sg-2, because count equals 2. Each Security Group has the same ingress and egress rules, differentiated only by their count.index.

Note: By default count.index is a zero-based index. I have added +1 ensures that the names and descriptions of the security groups start from 1 instead of 0

Below is the execution output of above terraform code:

PS C:\Users\charanjit\Documents\terraform_iteration_meta-arguments> terraform apply –auto-approve

 

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

+ create

 

Terraform will perform the following actions:

 

# module.sg_count.aws_security_group.count_sg[0] will be created

+ resource “aws_security_group” “count_sg” {

+ arn                    = (known after apply)

+ description            = “Security Group created using count index 1”

+ egress                 = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 0

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “-1”

+ security_groups  = []

+ self             = false

+ to_port          = 0

# (1 unchanged attribute hidden)

},

]

+ id                     = (known after apply)

+ ingress                = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 80

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “tcp”

+ security_groups  = []

+ self             = false

+ to_port          = 80

# (1 unchanged attribute hidden)

},

]

+ name                   = “web-sg-1”

+ name_prefix            = (known after apply)

+ owner_id               = (known after apply)

+ revoke_rules_on_delete = false

+ tags_all               = (known after apply)

+ vpc_id                 = (known after apply)

}

 

# module.sg_count.aws_security_group.count_sg[1] will be created

+ resource “aws_security_group” “count_sg” {

+ arn                    = (known after apply)

+ description            = “Security Group created using count index 2”

+ egress                 = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 0

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “-1”

+ security_groups  = []

+ self             = false

+ to_port          = 0

# (1 unchanged attribute hidden)

},

]

+ id                     = (known after apply)

+ ingress                = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 80

+ ipv6_cidr_blocks = []

+ to_port          = 0

# (1 unchanged attribute hidden)

},

]

+ id                     = (known after apply)

+ ingress                = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 80

+ ipv6_cidr_blocks = []

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “tcp”

+ security_groups  = []

+ self             = false

+ to_port          = 80

# (1 unchanged attribute hidden)

},

]

+ name                   = “web-sg-2”

+ name_prefix            = (known after apply)

+ owner_id               = (known after apply)

+ revoke_rules_on_delete = false

+ tags_all               = (known after apply)

+ vpc_id                 = (known after apply)

}

 

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

module.sg_count.aws_security_group.count_sg[1]: Creating…

module.sg_count.aws_security_group.count_sg[0]: Creating…

module.sg_count.aws_security_group.count_sg[1]: Creation complete after 2s [id=sg-0ec95c43a91685316]

module.sg_count.aws_security_group.count_sg[0]: Creation complete after 2s [id=sg-0c53af016b3bac972]

 

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

PS C:\Users\charanjit\Documents\terraform_iteration_meta-arguments>

 

Below are the screenshots of provisioned AWS Security Groups through count:

 

Conditional Creation with count

One of the strengths of count is its ability to conditionally create resources. For example, you might want to create a Security Group only if a certain variable is true:

variable “create_sg” {

default = true

}

 

resource “aws_security_group” “conditional_sg” {

count = var.create_sg ? 1 : 0

 

name        = “conditional-sg”

description = “This SG is created only if create_sg is true”

 

ingress {

from_port   = 80

to_port     = 80

protocol    = “tcp”

cidr_blocks = [“0.0.0.0/0”]

}

 

egress {

from_port   = 0

to_port     = 0

protocol    = “-1”

cidr_blocks = [“0.0.0.0/0”]

}

}

If create_sg is set to false, Terraform will skip creating the Security Group entirely. This makes count an excellent choice for switching the resource creation or avoids unnecessary resource creation and helps manage costs in the Cloud.

Below is the execution output of above terraform code:

PS C:\Users\charanjit\Documents\terraform_iteration_meta-arguments> terraform apply –auto-approve

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# module.sg_count_condition.aws_security_group.conditional_sg[0] will be created
+ resource “aws_security_group” “conditional_sg” {
+ arn = (known after apply)
+ description = “This Web Server conditional Security Group is created only if create_sg is true”
+ egress = [
+ {
+ cidr_blocks = [
+ “0.0.0.0/0”,
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = “-1”
+ security_groups = []
+ self = false
+ to_port = 0
# (1 unchanged attribute hidden)
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ “0.0.0.0/0”,
]
+ from_port = 80
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = “tcp”
+ security_groups = []
+ self = false
+ to_port = 80
# (1 unchanged attribute hidden)
},
]
+ name = “conditional_sg”
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}

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

Do you want to perform these actions?
Terraform will perform the actions described above.
Only ‘yes’ will be accepted to approve.

Enter a value: yes

module.sg_count_condition.aws_security_group.conditional_sg[0]: Creating…
module.sg_count_condition.aws_security_group.conditional_sg[0]: Creation complete after 2s [id=sg-061d8b0ec39ba607e]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

PS C:\Users\charanjit\Documents\terraform_iteration_meta-arguments>

 

Below is the screenshot of provisioned security group through count condition:

 

Security Group Provisioning with for_each

Now let’s see how to achieve the same result using for_each. This is especially useful if each Security Group requires unique attributes.

variable “sg_configs” {

default = {

sg1 = { name = “web-sg-1”, port = 80 },

sg2 = { name = “web-sg-2”, port = 443 }

}

}

 

resource “aws_security_group” “web_sg” {

for_each = var.sg_configs

 

name        = each.value.name

description = “Security Group for ${each.key}”

 

ingress {

from_port   = each.value.port

to_port     = each.value.port

protocol    = “tcp”

cidr_blocks = [“0.0.0.0/0”]

}

 

egress {

from_port   = 0

to_port     = 0

protocol    = “-1”

cidr_blocks = [“0.0.0.0/0”]

}

}

In this example:

  • We define a sg_configs variable, a map with unique names and ports for each Security Group.
  • for_each iterates over sg_configs, creating a Security Group for each key-value pair.
  • The each.key and each.value expressions dynamically set the Security Group’s name and ingress port.

The result is two Security Groups:

  • web-sg-1 with port 80 open.
  • web-sg-2 with port 443 open.

 

Below is the execution output of above terraform code:

PS C:\Users\charanjit\Documents\terraform_iteration_meta-arguments> terraform apply –auto-approve

 

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

+ create

 

Terraform will perform the following actions:

 

# module.security_groups_for_each.aws_security_group.for_each_sg[“sg1”] will be created

+ resource “aws_security_group” “for_each_sg” {

+ arn                    = (known after apply)

+ description            = “Security Group created for sg1”

+ egress                 = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 0

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “-1”

+ security_groups  = []

+ self             = false

+ to_port          = 0

# (1 unchanged attribute hidden)

},

]

+ id                     = (known after apply)

+ ingress                = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 80

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “tcp”

+ security_groups  = []

+ self             = false

+ to_port          = 80

# (1 unchanged attribute hidden)

},

]

+ name                   = “web-sg-1”

+ name_prefix            = (known after apply)

+ owner_id               = (known after apply)

+ revoke_rules_on_delete = false

+ tags_all               = (known after apply)

+ vpc_id                 = (known after apply)

}

 

# module.security_groups_for_each.aws_security_group.for_each_sg[“sg2”] will be created

+ resource “aws_security_group” “for_each_sg” {

+ arn                    = (known after apply)

+ description            = “Security Group created for sg2”

+ egress                 = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 0

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “-1”

+ security_groups  = []

+ self             = false

+ to_port          = 0

# (1 unchanged attribute hidden)

},

]

+ id                     = (known after apply)

+ ingress                = [

+ {

+ cidr_blocks      = [

+ “0.0.0.0/0”,

]

+ from_port        = 443

+ ipv6_cidr_blocks = []

+ prefix_list_ids  = []

+ protocol         = “tcp”

+ security_groups  = []

+ self             = false

+ to_port          = 443

# (1 unchanged attribute hidden)

},

]

+ name                   = “web-sg-2”

+ name_prefix            = (known after apply)

+ owner_id               = (known after apply)

+ revoke_rules_on_delete = false

+ tags_all               = (known after apply)

+ vpc_id                 = (known after apply)

}

 

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

module.security_groups_for_each.aws_security_group.for_each_sg[“sg1”]: Creating…

module.security_groups_for_each.aws_security_group.for_each_sg[“sg2”]: Creating…

module.security_groups_for_each.aws_security_group.for_each_sg[“sg2”]: Creation complete after 2s [id=sg-05190d6dade917b19]

module.security_groups_for_each.aws_security_group.for_each_sg[“sg1”]: Creation complete after 2s [id=sg-075986a50e78531de]

 

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

PS C:\Users\charanjit\Documents\terraform_iteration_meta-arguments>

 

Below are the screenshots of provisioned security groups through for_each:

 

Making the Right Choice

So, when should you use count, and when should you reach for for_each? Here’s a simple way to decide:

  • If all resources are identical or can be conditionally created based on a numeric count, use count.
  • If each resource needs unique attributes or configurations, use for_each.

For example:

  • Use count to create a fixed number of Security Groups with the same rules.
  • Use for_each when each Security Group has different rules, ports, or other attributes.

 

Wrap Up !

Terraform’s count and for_each constructs are both powerful tools for managing resources, but they excel in different scenarios. Understanding the uniqueness of each can save you time and frustration as you scale your infrastructure.

With count, you get simplicity and conditional creation. With for_each, you get customization and flexibility. By understanding both, you’ll have the tools to build dynamic, efficient Terraform configurations that suit any need.

You can check my Below GitHub link in which you can see how I have written code as per real-world examples:
https://github.com/cjcheema/terraform_iteration_meta-arguments/tree/main

In GitHub my code is structured as below:

tree terraform/
.
├── main.tf
├── terraform.tfvars
├── modules/
│ ├── sg_count/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ ├── sg_count_condition/
│ │ ├── main.tf
│ │ ├── variables.tf
│ ├── sg_for_each/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf

If you found this guide helpful, let me know in the comments or share it with your fellow Terraform enthusiasts!

Loading

  • Author
  • Recent Posts
Charanjit Singh
Follow him
Charanjit Singh
Charanjit is currently working as a Cloud Architect at Mphasis, with 18 years of experience in IT infrastructure projects, implementation, and support. While his main role is as a DevOps engineer, he holds a Cloud Architect position and has strong skills in cloud technologies and automation. His expertise includes Terraform, AWS, Azure DevOps, Azure Cloud, VMware, and Linux systems.

Charanjit is passionate about automating tasks and improving processes. He uses tools like Terraform and Azure DevOps to build and manage cloud infrastructure and streamline deployment. He also enjoys using Shell scripts and Ansible playbooks to make systems run more efficiently.

In his free time, Charanjit enjoys learning about new technologies and sharing his knowledge through his blog. When he’s not working, he likes listening to music, having a cup of coffee, and relaxing in nature.

You can connect with Charanjit on Twitter, Facebook, LinkedIn, or email him at charanjit.cheema@cjcheema.com.
Charanjit Singh
Follow him
Latest posts by Charanjit Singh (see all)
  • How to Deploy Docker Containers with NGINX on AWS EC2 Using Ansible and GitHub Actions - April 26, 2025
  • No More DynamoDB! Use Native S3 locking for Terraform State - February 7, 2025
  • How to Bring and Manage Manually Created AWS Resources Under Terraform Management - January 31, 2025

Like this:

Like Loading...

Related

Leave a ReplyCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Tags

AWS Cloud Computing Dockers Networking Open Networking OpenSource RHEL-CentOS SDN Server Hardware SLES tcpdump Ubuntu WSL

Follow me @

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 2 other subscribers

Recent Posts

  • How to Deploy Docker Containers with NGINX on AWS EC2 Using Ansible and GitHub Actions
  • No More DynamoDB! Use Native S3 locking for Terraform State
  • How to Bring and Manage Manually Created AWS Resources Under Terraform Management
  • Iterating Cloud Resource Provisioning Using Terraform Count and For_Each Meta-Arguments
  • Terraform and Ansible Collaboration for AWS Cloud Deployment

Recent Comments

  1. Charanjit Singh on Terraform and Ansible Collaboration for AWS Cloud Deployment
  2. christinatodd2020aeaa798563 on Terraform and Ansible Collaboration for AWS Cloud Deployment
  3. Charanjit Singh on How to Set password policy in CentOS or RHEL system
  4. SAURABH on How to recover or rebuild initramfs in CentOS 7 Linux
  5. Sangita on How to Set password policy in CentOS or RHEL system

Archives

  • April 2025
  • February 2025
  • January 2025
  • August 2024
  • July 2024
  • June 2024
  • January 2024
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • September 2022
  • August 2022
  • July 2020
  • May 2020
  • February 2020
  • November 2019
  • June 2019
  • May 2019
  • March 2019
  • February 2019
  • December 2018
  • November 2018
  • October 2018
  • September 2018
  • August 2018
  • June 2018
  • May 2018
  • April 2018

Categories

  • Automation
  • Cloud Computing
  • Coding
  • CyberSecurity
  • Networking
  • OpenSource
  • RHEL-CentOS
  • Server Hardware
  • SLES
  • Technical Blog
  • Ubuntu
  • WSL

Blog Stats

  • 18,353 hits
Privacy & Cookies: This site uses cookies. By continuing to use this website, you agree to their use.
To find out more, including how to control cookies, see here: Cookie Policy
  • Home
  • About Me!
  • Way to my Technical Blog
  • Contact me
  • Privacy Policy
© 2025 Welcome To Charanjit Cheema Blog | Powered by Superbs Personal Blog theme
 

Loading Comments...
 

    %d