Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 9 min read

Terraform Error Guide: 'Invalid count argument' (count depends on values not known until apply)

Fix Terraform's 'Invalid count argument' error when count depends on apply-time values: use plan-time values, for_each, or a two-stage targeted apply.

  • #terraform
  • #troubleshooting
  • #errors
  • #meta-arguments

Exact Error Message

When Terraform cannot figure out how many copies of a resource to build during the planning phase, it stops with an Invalid count argument error. A typical occurrence looks like this:

Error: Invalid count argument

  on main.tf line 12, in resource "aws_instance" "web":
  12:   count = length(data.aws_availability_zones.available.names)

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the count depends on.

The file name, line number, and the expression on the count = line will change to match your configuration, but the core sentence — “depends on resource attributes that cannot be determined until apply” — is always the same.

What the Error Means

Terraform runs in two phases: plan and apply. During plan, Terraform must build a complete dependency graph and decide exactly how many instances of each resource exist before it touches any cloud API. The count meta-argument is part of that structural decision — it determines how many addresses (aws_instance.web[0], aws_instance.web[1], and so on) appear in state.

The problem arises when the number you feed into count is unknown at plan time. Some values — like the IDs, ARNs, or computed lists returned by a resource that hasn’t been created yet — only become concrete during apply. Terraform represents these as (known after apply). If count depends on one of those, Terraform genuinely cannot decide how many instances to create when it builds the plan, so it refuses rather than guessing.

In short: count must be resolvable from data Terraform already knows (variables, locals, plan-time data sources), not from attributes that only exist after a resource is provisioned.

Common Causes

  • length() of a computed resource attribute. For example count = length(aws_subnet.private[*].id) when the subnets are also being created in the same plan. The IDs don’t exist yet.
  • count reading an attribute of another not-yet-created resource, such as count = var.create_lb ? length(aws_instance.web) : 0 where aws_instance.web is brand new.
  • A data source that depends on a managed resource. If a data block reads something created earlier in the same run, its results are unknown until that resource applies, and any count based on them inherits the unknown.
  • Conditionals on computed booleans, e.g. count = aws_db_instance.main.multi_az ? 2 : 1.
  • Module outputs that are themselves computed, then used to size count in a parent module.
  • for_each/count over the result of a for expression that iterates a computed list.

How to Reproduce the Error

This minimal configuration reliably triggers the error because the count on the security-group-rule resource depends on the IDs of instances being created in the same apply:

resource "aws_instance" "web" {
  count         = 3
  ami           = "ami-0abcd1234ef567890"
  instance_type = "t3.micro"
}

# This breaks: the instance IDs are unknown until apply
resource "aws_security_group_rule" "allow" {
  count             = length(aws_instance.web[*].id)
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["10.0.0.0/16"]
  security_group_id = "sg-0123456789abcdef0"
}

Run a plan and Terraform fails before showing any proposed changes:

terraform plan
Error: Invalid count argument

  on main.tf line 9, in resource "aws_security_group_rule" "allow":
   9:   count = length(aws_instance.web[*].id)

The "count" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.

Diagnostic Commands

Use only read-only commands to confirm which value is unknown. None of these change infrastructure.

Validate syntax and run a plan to surface the exact line:

terraform validate
terraform plan

Inspect the offending expression interactively. In terraform console, anything that prints (known after apply) is the culprit:

terraform console
> length(aws_instance.web)
(known after apply)
> var.availability_zones
tolist([
  "us-east-1a",
  "us-east-1b",
])

List what already exists in state — resources here are known and safe to count against:

terraform state list
terraform show

Grep your configuration for count lines that reference resource or data attributes rather than variables:

grep -rn "count" --include="*.tf" .
grep -rn "count *=.*\(aws_\|data\.\|module\.\)" --include="*.tf" .

Confirm the real, plan-time value from the provider directly, so you can hardcode or vary off it:

aws ec2 describe-availability-zones \
  --query "AvailabilityZones[?State=='available'].ZoneName" --output text

Step-by-Step Resolution

1. Identify the unknown input. From terraform console, find which sub-expression returns (known after apply). That value is what count cannot use.

2. Prefer a value known at plan time. Most often the real fix is to count against a variable or a local, not a resource attribute. Rewrite the broken example to size count from the same number that drives the instances:

variable "instance_count" {
  type    = number
  default = 3
}

resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = "ami-0abcd1234ef567890"
  instance_type = "t3.micro"
}

resource "aws_security_group_rule" "allow" {
  count             = var.instance_count   # known at plan time
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["10.0.0.0/16"]
  security_group_id = "sg-0123456789abcdef0"
}

3. Derive length from inputs, not outputs. When the size comes from a list, count the input list (a variable) instead of the computed attribute:

variable "availability_zones" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

resource "aws_subnet" "private" {
  count             = length(var.availability_zones)   # static, plan-time
  availability_zone = var.availability_zones[count.index]
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
}

Note that vpc_id being computed is fine — only count itself must be known, not every argument.

4. Switch to for_each over a known set. for_each keyed on a static map or set of strings is both more stable (no index churn on insert/delete) and easier for Terraform to plan, as long as the keys are known:

resource "aws_subnet" "private" {
  for_each          = toset(var.availability_zones)
  availability_zone = each.value
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(aws_vpc.main.cidr_block, 8, index(var.availability_zones, each.value))
}

5. As a last resort, do a two-stage targeted apply. When the count genuinely must depend on a resource you cannot precompute, apply that dependency first so its attributes become known, then apply the rest:

# Stage 1: create only the dependency
terraform apply -target=aws_vpc.main -target=aws_subnet.private

# Stage 2: now the counts resolve, apply everything
terraform apply

Treat -target as a temporary unblock, not a permanent pattern — it bypasses Terraform’s normal dependency ordering and is easy to forget in CI.

If you are working through this as part of a live outage and want a structured triage path, our incident response workflow can help you capture the rollback and re-apply steps.

Prevention and Best Practices

  • Count against inputs, never outputs. Variables and locals are known at plan time; resource attributes usually are not.
  • Compute lengths in locals so you have one named, reviewable place: local.subnet_count = length(var.availability_zones).
  • Prefer for_each with static keys for collections — it avoids both this error and the index-shifting destroy/recreate churn that plagues count.
  • Keep data sources independent of managed resources in the same run when their results feed count.
  • Run terraform plan in CI on every PR. This error always surfaces at plan, so it never has to reach an apply.
  • Browse more Terraform troubleshooting guides for related meta-argument pitfalls.

Frequently Asked Questions

Why does length() cause this when the list looks complete in my code? Because length() of a computed attribute (like aws_instance.web[*].id) measures a list whose contents don’t exist until apply. Terraform knows there will be a list, but not how many elements, so it cannot size count. Apply length() to a variable or local instead.

Is for_each immune to this error? No. for_each raises its own Invalid for_each argument error under the same conditions — its keys must be known at plan time. The difference is that for_each keys are usually static strings (AZ names, environment names), which are far easier to keep plan-time-known than computed numbers.

Can I just use -target permanently to avoid the problem? You can, but you shouldn’t. -target skips parts of the dependency graph and produces partial applies that are easy to misuse in automation. Use it to unblock a one-off, then refactor count to depend on plan-time values.

The argument is computed but I only use it for availability_zone, not count — why does it still fail? It doesn’t, if count itself is known. Only the count (or for_each) expression must be resolvable at plan time. Computed values in other arguments such as vpc_id or subnet_id are perfectly fine.

Will upgrading Terraform fix this? No. This is intentional behavior in every Terraform version, not a bug. The plan phase must know instance counts before apply, so the fix is always to give count a plan-time value.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.