Initializing Enclave...

How to Fix Terraform 'Invalid Index on List' When Using count.index with for_each

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 5–15 mins


TL;DR

  • What broke: You used count.index to index a list inside a resource that also declares for_each, or vice versa — Terraform's meta-argument system is mutually exclusive and panics on invalid list access.
  • How to fix it: Pick one iteration strategy. Use for_each with each.key / each.value, OR use count with var.list[count.index]. Never mix them on the same resource block.
  • Use our Client-Side Sandbox above to paste your failing .tf block and auto-refactor it instantly without exposing your config to external servers.

The Incident (What Does the Error Mean?)

Raw error output from terraform plan or terraform apply:

Error: Invalid index

  on main.tf line 14, in resource "aws_instance" "servers":
  14:   ami = var.ami_list[count.index]

The given key does not identify an element in this collection value.

Or the variant when for_each is active:

Error: Invalid use of count.index in for_each block

  on main.tf line 9, in resource "aws_subnet" "this":
  9:   cidr_block = var.cidrs[count.index]

count.index is only valid within a resource that uses the count meta-argument.

Immediate consequence: Terraform exits non-zero. No resources are created, modified, or destroyed. If this is mid-apply in a partial state, you may have orphaned resources with no corresponding state entry — a manual remediation nightmare.


The Blast Radius

This is not a soft warning. Terraform treats this as a hard panic:

  • count and for_each are mutually exclusive on any single resource or module block. Declaring both, or referencing count.index inside a for_each resource, is a compile-time type error in the Terraform evaluator.
  • In a monorepo or shared module, one bad resource block cascades: the entire root module fails to plan. Every downstream dependent workspace is blocked.
  • In CI/CD pipelines (Atlantis, Terraform Cloud, GitHub Actions), this causes a hard pipeline failure. If your merge queue depends on speculative plans, all open PRs are gated until fixed.
  • Partial applies that hit this mid-run can leave your state file referencing resources that were created before the error, with no for_each key or count index to track them — leading to terraform state rm surgery.

How to Fix It

Root Cause

You have one of three bad patterns:

  1. A resource declares for_each but references count.index internally.
  2. A resource declares count but the list length doesn't match what count.index expects at evaluation time (e.g., a dynamic list with unknown length at plan time).
  3. A module call passes both count and for_each simultaneously.

Basic Fix — Choose Your Iteration Primitive and Commit

Pattern A: You want to iterate over a list → use count

 resource "aws_instance" "servers" {
-  for_each = toset(var.instance_names)
-  ami      = var.ami_list[count.index]   # WRONG: count.index undefined here
+  count    = length(var.ami_list)
+  ami      = var.ami_list[count.index]   # CORRECT
   instance_type = "t3.micro"
 }

Pattern B: You want key-based tracking (safer for state) → use for_each

 resource "aws_instance" "servers" {
-  count    = length(var.ami_list)
-  ami      = var.ami_list[count.index]
+  for_each = { for idx, ami in var.ami_list : idx => ami }
+  ami      = each.value
   instance_type = "t3.micro"
 }

Enterprise Best Practice — for_each with a Map Input

Stop passing raw lists to modules or resources. Lists are index-fragile — inserting an element at position 0 forces Terraform to destroy and recreate everything. Use maps.

 # variables.tf
-variable "ami_list" {
-  type = list(string)
-}
+variable "instances" {
+  type = map(object({
+    ami           = string
+    instance_type = string
+  }))
+}

 # main.tf
 resource "aws_instance" "servers" {
-  count         = length(var.ami_list)
-  ami           = var.ami_list[count.index]
-  instance_type = "t3.micro"
+  for_each      = var.instances
+  ami           = each.value.ami
+  instance_type = each.value.instance_type
+
+  tags = {
+    Name = each.key
+  }
 }

With this pattern, adding or removing a single instance only affects that one state key. No collateral destroy/recreate.


💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. terraform validate as a Pre-Commit Gate

This error is caught at validate time, before any API calls. Add it to your pre-commit hooks:

# .pre-commit-config.yaml
- repo: https://github.com/antonbabenko/pre-commit-terraform
  hooks:
    - id: terraform_validate
    - id: terraform_tflint

2. TFLint Rule — terraform_required_version + Iterator Checks

# .tflint.hcl
plugin "terraform" {
  enabled = true
  preset  = "recommended"  # catches mixed count/for_each patterns
}

3. Checkov Policy — Enforce Map Inputs on Shared Modules

Write a custom Checkov check that flags any variable of type = list(string) used directly in a resource iteration without conversion to a map.

4. OPA/Conftest Policy in Atlantis

# policy/no_count_index_in_foreach.rego
deny[msg] {
  resource := input.resource_changes[_]
  resource.change.actions[_] == "create"
  # Flag resources where plan shows both for_each keys and count-style numeric indexes
  is_number(resource.index)
  msg := sprintf("Resource %v uses numeric index — migrate to for_each with map input.", [resource.address])
}

5. Terraform Cloud Sentinel (Hard Fail)

# sentinel policy
import "tfplan/v2" as tfplan

main = rule {
  all tfplan.resource_changes as _, rc {
    rc.index is not number  # enforce string keys (for_each) over numeric (count)
  }
}

Enforce this as a hard-mandatory policy in your TFC organization. Numeric count indexes in production state are a liability — one list reorder triggers mass resource replacement.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →