Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Terraform `count` and `for_each` Meta-Arguments: Zero-Fluff, Hands-On Guide**
Source: https://www.fatskills.com/cloud-application-developer/chapter/tech-terraform-count-and-for-each-meta-arguments-zero-fluff-hands-on-guide

TECH **Terraform `count` and `for_each` Meta-Arguments: Zero-Fluff, Hands-On Guide**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~8 min read

Terraform count and for_each Meta-Arguments: Zero-Fluff, Hands-On Guide


1. What This Is & Why It Matters

You’re deploying 10 identical EC2 instances for a web app tier. Or 50 IAM users with the same permissions. Or 3 S3 buckets with slightly different names but identical policies.

Do you: - Copy-paste the same resource block 10 times? (No. That’s a maintenance nightmare.) - Use a shell script to loop and call terraform apply in a loop? (No. That breaks Terraform’s state tracking.) - Use count or for_each? (Yes. This is the only way to dynamically create multiple instances of a resource while keeping Terraform’s state clean.)

Why This Matters in Production

  • Avoids "copy-paste hell" – If you need to update a security group rule, you change one block, not 50.
  • Keeps state predictable – Terraform tracks each instance separately (e.g., aws_instance.web[0], aws_instance.web[1]).
  • Enables dynamic scaling – Need 5 more instances? Change count = 5 to count = 10. Terraform handles the rest.
  • Prevents drift – If someone manually deletes aws_instance.web[3], Terraform will recreate it on the next apply.

Real-world scenario:
You’re migrating a legacy app to AWS. The app needs 3 identical web servers, 2 app servers, and 1 database. Instead of writing 6 separate aws_instance blocks, you use count or for_each to define them once, then let Terraform handle the rest.


2. Core Concepts & Components


count Meta-Argument

  • What it is: Creates multiple identical resources based on a numeric index.
  • Syntax:
    hcl resource "aws_instance" "web" {
    count = 3 # Creates 3 instances: web[0], web[1], web[2]
    ami = "ami-0c55b159cbfafe1f0"
    instance_type = "t2.micro" }
  • Production insight:
  • If you remove an instance (e.g., count = 3count = 2), Terraform destroys the last one (web[2]).
  • ⚠️ Problem: If you reorder resources (e.g., web[1] becomes web[0]), Terraform destroys and recreates them (bad for stateful resources like databases).

for_each Meta-Argument

  • What it is: Creates multiple resources with unique identifiers (not just numeric indices).
  • Syntax:
    hcl resource "aws_iam_user" "developers" {
    for_each = toset(["alice", "bob", "charlie"]) # Creates 3 users: alice, bob, charlie
    name = each.key }
  • Production insight:
  • More stable than count – If you remove "bob" from the list, Terraform only destroys bob, not all users.
  • Works with maps (key-value pairs):
    ```hcl
    variable "users" {
    type = map(object({
    role = string
    }))
    default = {
    alice = { role = "admin" }
    bob = { role = "dev" }
    }
    }

    resource "aws_iam_user" "example" { for_each = var.users name = each.key tags = {
    Role = each.value.role } } ```

toset() and tomap() Functions

  • What they are: Convert lists/maps into sets/maps for for_each.
  • Example:
    hcl for_each = toset(["a", "b", "c"]) # Converts list to set
  • Production insight:
  • Sets are unordered – If order matters (e.g., for count), use a list.
  • Maps require unique keys – If you have duplicate keys, Terraform fails.

each.key and each.value

  • What they are: References to the current item in for_each.
  • each.key = The key (for maps) or value (for sets).
  • each.value = The value (for maps).
  • Example:
    hcl resource "aws_s3_bucket" "example" {
    for_each = {
    logs = "us-east-1"
    backups = "us-west-2"
    }
    bucket = "my-bucket-${each.key}"
    region = each.value }
  • Production insight:
  • Use each.key for naming (e.g., bucket = "app-${each.key}").
  • Use each.value for configurations (e.g., region = each.value).

count.index

  • What it is: The numeric index (0, 1, 2, ...) when using count.
  • Example:
    hcl resource "aws_instance" "web" {
    count = 3
    ami = "ami-0c55b159cbfafe1f0"
    instance_type = "t2.micro"
    tags = {
    Name = "web-${count.index}"
    } }
  • Production insight:
  • Avoid using count.index for stateful resources (e.g., databases). If you remove count = 3count = 2, Terraform destroys the last instance, which could be a production DB.

When to Use count vs. for_each

Use Case count for_each
Identical resources (e.g., 5 web servers) ✅ Best ❌ Works, but overkill
Unique configurations (e.g., S3 buckets in different regions) ❌ Bad ✅ Best
Stable resource names (e.g., IAM users) ❌ Risky (reordering breaks state) ✅ Best
Dynamic scaling (e.g., add/remove instances) ⚠️ Works, but risky ✅ Best


3. Step-by-Step Hands-On: Deploying Multiple EC2 Instances


Prerequisites

  • AWS account with admin IAM permissions.
  • Terraform installed (terraform --version should work).
  • AWS CLI configured (aws sts get-caller-identity should return your user).

Task: Deploy 3 Identical Web Servers Using count

  1. Initialize a new Terraform project:
    bash
    mkdir tf-count-demo && cd tf-count-demo
    touch main.tf

  2. Define the aws_instance resource with count:
    ```hcl
    provider "aws" {
    region = "us-east-1"
    }

resource "aws_instance" "web" {
count = 3 # Creates 3 instances
ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 (us-east-1)
instance_type = "t2.micro"
tags = {
Name = "web-${count.index}"
}
}
```


  1. Apply the configuration:
    bash
    terraform init
    terraform plan
    terraform apply -auto-approve

  2. Verify the instances:
    bash
    aws ec2 describe-instances --filters "Name=tag:Name,Values=web-*" --query "Reservations[*].Instances[*].[InstanceId,Tags[?Key=='Name'].Value|[0]]" --output table

    Expected output:
    ```


| DescribeInstances |
+--------------+-----------------------+
| i-012345678 | web-0 |
| i-876543210 | web-1 |
| i-123456789 | web-2 |
+--------------+-----------------------+
```


  1. Clean up:
    bash
    terraform destroy -auto-approve

Task: Deploy IAM Users with Unique Roles Using for_each

  1. Update main.tf:
    ```hcl
    provider "aws" {
    region = "us-east-1"
    }

variable "users" {
type = map(object({
role = string
}))
default = {
alice = { role = "admin" }
bob = { role = "dev" }
charlie = { role = "read-only" }
}
}

resource "aws_iam_user" "example" {
for_each = var.users
name = each.key
tags = {
Role = each.value.role
}
}
```


  1. Apply the configuration:
    bash
    terraform apply -auto-approve

  2. Verify the users:
    bash
    aws iam list-users --query "Users[].UserName" --output table

    Expected output:
    ```


| ListUsers |
+-------------------+
| alice |
| bob |
| charlie |
+-------------------+
```


  1. Clean up:
    bash
    terraform destroy -auto-approve

4. ? Production-Ready Best Practices


Security

  • Avoid hardcoding secrets – Use sensitive = true for variables containing passwords/API keys.
    hcl variable "db_password" {
    type = string
    sensitive = true }
  • Least privilege IAM – If using for_each for IAM users, restrict permissions per user.
    hcl resource "aws_iam_user_policy_attachment" "example" {
    for_each = aws_iam_user.example
    user = each.key
    policy_arn = "arn:aws:iam::aws:policy/${each.value.role}" }

Cost Optimization

  • Use count for ephemeral resources (e.g., CI/CD runners that scale up/down).
  • Use for_each for long-lived resources (e.g., databases, IAM users) to avoid accidental deletions.

Reliability & Maintainability

  • Tag everything – Helps with cost tracking and automation.
    hcl resource "aws_instance" "web" {
    count = 3
    tags = {
    Name = "web-${count.index}"
    Environment = "prod"
    Terraform = "true"
    } }
  • Use for_each for stateful resources (e.g., databases) to avoid accidental deletions when scaling down.
  • Avoid count for resources with dependencies – If aws_instance.web[2] depends on aws_db_instance.main, removing web[2] could break the DB.

Observability

  • Log Terraform changes – Use AWS CloudTrail to track who ran terraform apply.
  • Monitor drift – Use terraform plan in CI/CD to detect manual changes.


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Using count for stateful resources (e.g., databases) If you reduce count, Terraform destroys the last instance, which could be a production DB. Use for_each instead.
Reordering a list used with count Terraform destroys and recreates resources (e.g., web[0] becomes web[1]). Use for_each with a map/set.
Forgetting toset() or tomap() Terraform fails with Invalid for_each argument: value must be a map or set of strings. Always convert lists/maps to sets/maps.
Using count.index in resource names If you remove an instance, Terraform renames all subsequent instances (e.g., web-1web-0). Use for_each with unique keys.
Not using sensitive = true for secrets Secrets appear in plaintext in Terraform state. Mark sensitive variables with sensitive = true.


6. ? Exam/Certification Focus


Typical Question Patterns

  1. "You need to deploy 5 identical EC2 instances. Which meta-argument should you use?"
  2. count (best for identical resources).
  3. for_each (overkill for identical resources).

  4. "You need to create IAM users with different permissions. Which meta-argument is best?"

  5. for_each (best for unique configurations).
  6. count (risky if users are removed).

  7. "What happens if you change count = 3 to count = 2?"

  8. ✅ Terraform destroys the last instance (web[2]).
  9. ❌ Terraform destroys the first instance (web[0]).

  10. "How do you reference the current item in for_each?"

  11. each.key (for the key) and each.value (for the value).
  12. count.index (only for count).

Key ⚠️ Trap Distinctions

  • count vs. for_each for stateful resources:
  • count = Destroys the last instance when scaling down.
  • for_each = Only destroys the removed item.
  • toset() vs. tomap():
  • toset() = Converts a list to a set (unordered, unique).
  • tomap() = Converts a list of objects to a map (key-value pairs).


7. ? Hands-On Challenge (with Solution)


Challenge:

Deploy 3 S3 buckets with the following names: - my-app-logs-us-east-1 - my-app-backups-us-west-2 - my-app-assets-eu-west-1

Use for_each and a map of regions.

Solution:

provider "aws" {
  region = "us-east-1"
}

variable "buckets" {
  type = map(string)
  default = {
logs = "us-east-1"
backups = "us-west-2"
assets = "eu-west-1" } } resource "aws_s3_bucket" "example" { for_each = var.buckets bucket = "my-app-${each.key}-${each.value}" region = each.value }

Why It Works:

  • for_each iterates over the buckets map.
  • each.key = logs, backups, assets.
  • each.value = us-east-1, us-west-2, eu-west-1.


8. ? Rapid-Reference Crib Sheet

Concept Syntax Example ⚠️ Trap
count count = N count = 3 Reducing count destroys the last instance.
for_each (set) for_each = toset(["a", "b"]) for_each = toset(["alice", "bob"]) Must use toset() for lists.
for_each (map) for_each = { key = "value" } for_each = { logs = "us-east-1" } Keys must be unique.
each.key each.key name = each.key For sets, each.key = value.
each.value each.value region = each.value Only for maps.
count.index count.index name = "web-${count.index}" Avoid for stateful resources.
sensitive sensitive = true variable "password" { sensitive = true } Prevents secrets in logs.


9. ? Where to Go Next

  1. Terraform count Docs
  2. Terraform for_each Docs
  3. Terraform toset() and tomap() Docs
  4. AWS Terraform Best Practices

Final Thought

count and for_each are the difference between "I’ll just copy-paste this 50 times" and "I’ll let Terraform handle it."


  • Use count for identical, ephemeral resources (e.g., web servers).
  • Use for_each for unique, long-lived resources (e.g., databases, IAM users).
  • Never use count for stateful resources (e.g., databases) – you’ll regret it when Terraform destroys your production DB.

Now go deploy something! ?



ADVERTISEMENT