TerraformTerraform

Error: Invalid count argument

count needs a number known at plan time and yours depends on something created in the same run. How to restructure, and when for_each is the better answer.

hard fix6 min read

the terraform error
Error: Invalid count argument
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.

Error: Invalid count argument
The given "count" argument value is unsuitable: a number is required.

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Find which part of the count expression is unknown

    terraform plan -no-color 2>&1 | grep -B5 -A10 "Invalid count argument"

    The error quotes the expression. Anything referencing an attribute of a resource being created in the same run is unknown at plan time, and length() of an unknown list is itself unknown.

  2. 2

    Evaluate the expression against current state

    terraform console

    Paste the count expression into the console. A result of (known after apply) confirms the cause. A concrete number means state already has it and the problem is elsewhere.

  3. 3

    Re-express the count in terms of inputs rather than outputs

    terraform validate

    A count derived from a variable, a local or a literal is known before anything is created. Moving the unknown value out of count and into the resource body resolves this without -target.

All 9 sections

Terraform must know how many instances a resource will have before it creates anything, because the plan is a precise statement of which addresses will change. A count that depends on an attribute of something being created in the same run cannot be known, so it refuses.

Two variants share the heading:

Unknown value. The count depends on a resource attribute. Structural problem.

Wrong type. The count is not a number. One-line fix.

The wrong type case

variable "azs" {
  type = list(string)
}

resource "aws_subnet" "private" {
  count = var.azs          # a list, not a number
}
  count = length(var.azs)

For a boolean toggle:

  count = var.create_bucket ? 1 : 0

count accepts a whole number and nothing else.

The unknown value case

resource "aws_instance" "app" {
  count = 3
}

resource "aws_volume_attachment" "data" {
  count       = length(aws_instance.app)         # fine
  instance_id = aws_instance.app[count.index].id
}

That works, because length() of a list whose size is known from a literal count is known.

This does not:

data "aws_instances" "existing" {
  filter { name = "tag:Env" values = ["prod"] }
}

resource "aws_eip" "app" {
  count = length(data.aws_instances.existing.ids)   # unknown if the data source
}                                                    # depends on this run

A data source that depends on something created in the same apply cannot be read at plan time, so everything it returns is unknown, and length() of an unknown list is unknown.

Confirm in the console:

terraform console
> length(data.aws_instances.existing.ids)
(known after apply)

Fix: express the count from inputs

The rule is the same as for for_each: the count must come from variables, locals or literals. The body may use unknown values.

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

resource "aws_instance" "app" {
  count = var.instance_count
}

resource "aws_eip" "app" {
  count    = var.instance_count                     # known now
  instance = aws_instance.app[count.index].id       # resolved at apply
}

Reusing the same variable rather than deriving one from the other keeps both counts known.

Prefer for_each where there is an identity

count addresses by position, so removing an element renumbers everything after it:

variable "buckets" { default = ["logs", "assets", "backups"] }

resource "aws_s3_bucket" "this" {
  count  = length(var.buckets)
  bucket = var.buckets[count.index]
}

Remove logs and assets moves from index 1 to index 0, so Terraform destroys and recreates both remaining buckets. On an S3 bucket that is data loss.

resource "aws_s3_bucket" "this" {
  for_each = toset(var.buckets)
  bucket   = each.key
}

Addresses become aws_s3_bucket.this["assets"] and removing one leaves the others untouched. Use count only for genuinely interchangeable copies, or for a conditional 0 or 1.

Migrating between them requires moving state, which moved blocks handle:

moved {
  from = aws_s3_bucket.this[0]
  to   = aws_s3_bucket.this["logs"]
}

Conditional resources

resource "aws_cloudwatch_log_group" "app" {
  count             = var.enable_logging ? 1 : 0
  name              = "/aws/app"
  retention_in_days = 30
}

output "log_group_arn" {
  value = var.enable_logging ? aws_cloudwatch_log_group.app[0].arn : null
}

Referencing [0] when the count is zero fails, so guard it. one() is cleaner:

  value = one(aws_cloudwatch_log_group.app[*].arn)

one() returns the single element of a one-element list, or null for an empty one, which is exactly the conditional-resource case.

The condition itself must be known at plan time, so var.enable_logging works and length(data.something.unknown) > 0 does not.

Modules

module "app" {
  source = "./modules/app"
  count  = length(module.platform.cluster_ids)    # unknown on first apply
}

A module's outputs are unknown until it has been applied, so a count derived from them fails on the first run and succeeds afterwards, which is a confusing intermittent failure in a pipeline. Pass a variable instead:

variable "cluster_count" { type = number }

module "app" {
  source = "./modules/app"
  count  = var.cluster_count
}

About -target

The error suggests it, and it does work: apply the dependency, then the count is known.

HashiCorp's documentation describes -target as being for exceptional recovery rather than routine use, and the reason matters. Applying part of a configuration leaves state consistent with neither the old nor the new configuration. A pipeline that needs it on every run has a plan-time dependency it should not have.

Use it once to get unstuck, then restructure.

A checklist

  1. Which variant: a wrong type, or an unknown value?
  2. Wrong type → length(), or a ? 1 : 0 conditional.
  3. terraform console and evaluate the expression. (known after apply) confirms it.
  4. Move the unknown out of count and into the resource body.
  5. Derive both counts from the same variable rather than from each other.
  6. Natural identity → use for_each, not count.
  7. Conditional resources → one() rather than [0].
  8. -target unblocks you once. Do not put it in a pipeline.

Frequently Asked Questions

Why must count be known before apply?

Because the count determines how many resource instances exist and what their addresses are, such as aws_instance.app[2], and a plan is a precise commitment about which addresses will be created, changed or destroyed. If the count were unknown, Terraform could not tell you how many resources it was about to touch or detect that one had been removed. Values inside the resource body may be unknown and simply show as (known after apply), because they do not affect identity or cardinality.

Should I use count or for_each?

for_each whenever the items have a natural identifier, and count only for interchangeable copies or a conditional zero-or-one. The difference shows when the set changes: with count, resources are addressed by position, so removing an early element renumbers everything after it and Terraform destroys and recreates resources that did not change. On stateful resources such as S3 buckets or databases that is data loss. With for_each each instance keeps its key and is unaffected.

How do I reference a resource that might not exist?

Use one(), which returns the single element of a one-element list or null if the list is empty: one(aws_cloudwatch_log_group.app[*].arn). Indexing with [0] fails outright when the count is zero, and wrapping it in a conditional works but is more verbose. The condition controlling the count must itself be known at plan time, so a variable works while something derived from an unapplied resource does not.

Why does my module count fail only on the first apply?

Because it derives from another module's output, and outputs are unknown until that module has been applied. On the first run there is nothing in state, so the value is unknown and the count cannot be computed. On subsequent runs state holds it and the plan succeeds, which makes this look like an intermittent problem. Pass the value in as a variable so it is known before Terraform starts, rather than deriving it from the graph.

Is -target an acceptable fix?

As a one-off, yes; as part of a pipeline, no. It works because applying the dependency first makes the count knowable. The cost is that a targeted apply leaves state matching neither the previous nor the intended configuration, so anyone applying in between sees a partially built world. HashiCorp documents it as a recovery tool. If your pipeline needs it on every run, the configuration has a plan-time dependency that should be restructured away.

Reference and practice

Learn the underlying concept

Other Terraform errors