Ravindra BagaleCourses & study guides मराठी

Chapter 36: Terraform count for_each dynamic lifecycle and depends_on

36.2 for_each — named copies that survive edits

Story. A Netflix-style team kept one S3 bucket per microservice in a list with count. Deleting the second service from the list made Terraform want to destroy and recreate the third bucket, because its index changed. for_each keys every copy by a stable name instead of a number. Removing one key removes only that one resource.

set / map for_each bucket ["auth"] bucket ["feed"] each.key / each.value
for_each keys copies by name, so removing one key touches only that one resource.
variable "services" {
  type = map(object({
    versioning = bool
  }))
  default = {
    auth = { versioning = true }
    feed = { versioning = false }
  }
}

resource "aws_s3_bucket" "svc" {
  for_each = var.services
  bucket   = "badge-${each.key}-<your-account-id>"
  tags     = { Service = each.key }
}

resource "aws_s3_bucket_versioning" "svc" {
  for_each = { for k, v in var.services : k => v if v.versioning }
  bucket   = aws_s3_bucket.svc[each.key].id
  versioning_configuration { status = "Enabled" }
}

What each line does

  • type = map(object({ versioning = bool })) — Each map entry is an object with one true or false field.
  • default = { auth = ..., feed = ... } — Two services keyed by name.
  • for_each = var.services — Creates one bucket per map key.
  • bucket = "badge-${each.key}-..." — each.key is auth or feed.
  • for_each = { for k, v in var.services : k => v if v.versioning } — A for expression keeps only the services that want versioning.
  • bucket = aws_s3_bucket.svc[each.key].id — Looks up the matching bucket by the same key.

When it is green.

  # aws_s3_bucket.svc["auth"] will be created
  # aws_s3_bucket.svc["feed"] will be created
  # aws_s3_bucket_versioning.svc["auth"] will be created
Plan: 3 to add, 0 to change, 0 to destroy.

When it is red.

Error: Invalid for_each argument
  The given "for_each" argument value is unsuitable: the "for_each" argument must be a map,
  or set of strings, and you have provided a value of type list of string.

Fix. Wrap a list in toset(var.names), or convert it to a map. Keys must be known at plan time.

What: every term in plain English

  • for_each — A meta-argument. It makes one copy per key in a map or set.
  • each.key — The key of the current copy, such as "auth".
  • each.value — The value for that key. For a set it equals each.key.
  • toset() — Turns a list into a set of unique strings.
  • for expression — Builds a new map or list, such as a filtered map.
  • instance key — The name in square brackets, such as svc["auth"].

Example 1: Named files that survive edits

The same three services as the count trap. Now remove the middle one and see only one change.

variable "names" {
  type    = set(string)
  default = ["auth", "feed", "search"]
}

resource "local_file" "svc" {
  for_each = var.names
  filename = "${path.module}/out/${each.key}.txt"
  content  = each.key
}
terraform apply -auto-approve
terraform plan -var 'names=["auth","search"]' | grep -E "destroyed|Plan:"

Output

  # local_file.svc["feed"] will be destroyed
Plan: 0 to add, 0 to change, 1 to destroy.

What it shows: Only feed goes. auth and search keep their addresses because the key is the name.

Example 2: A map with settings per service

A Flipkart-style team gives each service its own retention days. A map carries the setting with the key.

variable "services" {
  type = map(object({
    days = number
  }))
  default = {
    auth   = { days = 30 }
    orders = { days = 365 }
  }
}

resource "local_file" "policy" {
  for_each = var.services
  filename = "${path.module}/out/${each.key}-retention.txt"
  content  = "${each.key} keeps logs for ${each.value.days} days"
}

output "policies" {
  value = { for k, f in local_file.policy : k => f.content }
}
terraform apply -auto-approve
terraform output policies

Output

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

{
  "auth" = "auth keeps logs for 30 days"
  "orders" = "orders keeps logs for 365 days"
}

What it shows: The output is a map keyed by service, easy for other modules to read.

Example 3: Filter with a for expression

Only some buckets need versioning. Filter the map so for_each creates only those.

locals {
  needs_versioning = { for k, v in var.services : k => v if v.days > 90 }
}

resource "local_file" "versioned" {
  for_each = local.needs_versioning
  filename = "${path.module}/out/${each.key}-versioned.txt"
  content  = "versioning on"
}
terraform plan | grep -E "will be created|Plan:"

Output

  # local_file.versioned["orders"] will be created
Plan: 1 to add, 0 to change, 0 to destroy.

What it shows: auth has 30 days, so it is filtered out. Change auth to 120 days and it joins.

Example 4: Move from count to for_each without rebuilding

Old code at an Infosys client used count. Switch to for_each with moved blocks so nothing is destroyed.

resource "local_file" "svc" {
  for_each = toset(["auth", "feed", "search"])
  filename = "${path.module}/out/${each.key}.txt"
  content  = each.key
}

moved {
  from = local_file.svc[0]
  to   = local_file.svc["auth"]
}
moved {
  from = local_file.svc[1]
  to   = local_file.svc["feed"]
}
moved {
  from = local_file.svc[2]
  to   = local_file.svc["search"]
}
terraform plan | tail -1

Output

  # local_file.svc[0] has moved to local_file.svc["auth"]
  # local_file.svc[1] has moved to local_file.svc["feed"]
  # local_file.svc[2] has moved to local_file.svc["search"]
Plan: 0 to add, 0 to change, 0 to destroy.

What it shows: Zero changes means the move is safe. Keep the moved blocks for one release, then delete them.

Example 5: Real AWS: one bucket per service

A Netflix-style team keeps one S3 bucket per microservice. With for_each, adding a new service is one new line in the map, and removing one touches only that bucket.

  1. Put the services in a set.
  2. Apply.
  3. Add a new service and plan again.
data "aws_caller_identity" "me" {}

resource "aws_s3_bucket" "svc" {
  for_each = toset(["auth", "feed", "search"])
  bucket   = "svc-${each.key}-${data.aws_caller_identity.me.account_id}"
  tags = {
    Service = each.key
  }
}
terraform apply -auto-approve
terraform state list | grep aws_s3_bucket

Output

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
aws_s3_bucket.svc["auth"]
aws_s3_bucket.svc["feed"]
aws_s3_bucket.svc["search"]

What it shows: Add "payments" to the set and the plan shows exactly 1 to add. No other bucket moves.

Command cheat sheet

Command What it does When to use it
for_​each = toset(list) One copy per unique string Simple names
for_​each = var.​map One copy per key with settings Per-item config
each.​key / each.​value Current key and value Inside the block
res["key"].attr One copy by key References
{ for k, v in m : k => v if cond } Filtered map Some items only
moved { from = r[0] to = r["a"] } Change address safely count to for_each

Verify: did it work?

  1. Keys in state

    terraform state list
    

    Output

    local_file.svc["auth"]
    local_file.svc["search"]
    
  2. One value

    terraform console
    

    Output

    > local_file.policy["orders"].content
    "orders keeps logs for 365 days"
    

When it is red 1

Error: Invalid for_each argument
you have provided a value of type list of string.

Cause. for_each needs a set or map.

Fix, step by step

  1. Wrap the list with toset().
  2. Or build a map with a for expression.

When it is red 2

Error: Invalid for_each argument
The "for_each" set includes values derived from resource attributes that cannot be determined until apply

Cause. A key depends on an id made during apply.

Fix, step by step

  1. Use names you know, such as variable values, as keys.
  2. Put unknown ids in the values, not the keys.

When it is red 3

Error: Invalid index
  on main.tf line 12: aws_s3_bucket.svc["Feed"]
The given key does not identify an element in this collection value.

Cause. Wrong key, often a case mistake.

Fix, step by step

  1. Run terraform state list to see the exact keys.
  2. Use the same spelling.

Cleanup

terraform destroy -auto-approve
rm -rf out

Ravindra Bagale's Tip

Keys are forever. Pick short, stable names like auth or orders, never something that may change, such as a display title.

Practice tasks

Easy Set of names

Why: First for_each.

What to do: Make three files with toset.

How:
  1. Use local_file.

Check: Three keys in state.

Medium Remove one

Why: See the benefit.

What to do: Remove the middle name and plan.

How:
  1. Compare with the count lesson.

Check: 1 to destroy only.

Medium Map settings

Why: Real config.

What to do: Give each service its own days.

How:
  1. Use map(object).

Check: Output shows each days value.

Hard Migrate

Why: No downtime refactor.

What to do: Convert a count resource to for_each with moved blocks.

How:
  1. Plan after.

Check: 0 to add, 0 to change, 0 to destroy.

Easy Say it in two lines

Why: If you can say it simply, you understand it.

What to do: Write two short lines about this lesson for a fresher.

How:
  1. Write the problem it solves.
  2. Write the one command or file you use.
  3. Read it aloud.

Check: A fresher understands it without a question.

Easy Start a safe lab folder

Why: Practice needs a throwaway place.

What to do: Make a new folder with versions.tf and a random_pet resource, so no AWS cost.

How:
  1. mkdir tf-lab and cd into it.
  2. Add the hashicorp/random provider and one random_pet.
  3. Run terraform init and terraform apply.

Check: Apply complete! with 1 added.

Medium Break it on purpose

Why: You must know the red output by face.

What to do: Cause one error from the red part of this lesson and fix it.

How:
  1. Follow one red section.
  2. Copy the exact message to your notes.
  3. Write the fix in one line.

Check: You fixed it without searching the web.

Medium Run it twice

Why: A second run must be quiet.

What to do: Run the same plan two times with no edits.

How:
  1. Run terraform plan.
  2. Run it again.
  3. Explain any change it still shows.

Check: The second plan says No changes, or you can explain each line.

Medium Write a runbook page

Why: At 2 a.m. nobody remembers flags.

What to do: Write a one-page runbook for this lesson.

How:
  1. Title, when to use, exact commands, how to check, how to undo.

Check: A teammate can follow it alone.

Medium Run it from a pipeline

Why: Real teams run Terraform from CI, not from laptops.

What to do: Run the main command of this lesson inside a GitHub Actions or Jenkins job.

How:
  1. Add a job with hashicorp/setup-terraform or a Terraform Docker image.
  2. Give it AWS access through an IAM role, never a key in the file.
  3. Run init, then the command from this lesson.

Check: The job log shows the same output you saw on your laptop.

Medium Check the cost

Why: Labs left running cost real money.

What to do: Find what this lesson would cost per day if you forgot to destroy it.

How:
  1. Open the AWS Pricing Calculator.
  2. Add each resource from your plan.
  3. Write the daily cost in your notes.

Check: You know the daily cost and you ran terraform destroy.

Hard Draw the flow top to bottom

Why: A drawing shows the order of work.

What to do: Draw this lesson as boxes from top to bottom.

How:
  1. Start at your code, go through plan and state, end at AWS.
  2. Mark where each command reads or writes.

Check: Each box matches a real step you ran.

Hard Explain it like an interview

Why: Interviewers want real stories.

What to do: Record a 60 second answer about this lesson.

How:
  1. Say the problem, the command, and one error you hit.

Check: Your answer has a real command and a real error message.

Homework: try at home

Try at home

Write the bucket list for your own project as a map with one setting per bucket, and create it with for_each.

Recap

  • for_each keys copies by name.
  • Removing a key touches only that copy.
  • Use a map to carry settings per item.
  • Filter with a for expression.
  • Move from count with moved blocks.

FAQ

Set or map?

Set for names only. Map when each item has settings.

Can keys come from a data source?

Only if known at plan. Usually use variables.

Does order matter in a set?

No. Sets have no order, which is why keys stay stable.

Can I for_each a module?

Yes, since Terraform 0.13.

What is each.value for a set?

The same as each.key.

Interview questions

  1. Why prefer for_each over count?

    Keys are stable, so removing one item does not rebuild others.

  2. What types does for_each accept?

    A map or a set of strings.

  3. How do you convert count to for_each safely?

    Add moved blocks from each index to each key.

  4. How do you create only some items?

    Filter the map with a for expression and an if.

  5. Where do you put unknown values?

    In each.value, never in the keys.

Quick quiz

Q1. Function to turn a list into a set?

toset()

Q2. Address of the auth copy?

res["auth"]

Q3. For a set, each.value equals?

each.key

Q4. Can keys be unknown until apply?

No.

Q5. Block to rename addresses?

moved

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards