Skip to content
Infrastructure as Code iac resources 4 min read

count

By default a Terraform resource block manages a single real-world object. The count meta-argument changes that: it tells Terraform to create and manage N near-identical instances from one block. This is the simplest way to scale a resource up or down, and—because count accepts any whole number, including zero—it doubles as a clean idiom for conditionally creating infrastructure. count works identically in both Terraform 1.5+ and OpenTofu.

Creating multiple instances

Set count to a positive integer and Terraform produces that many instances of the resource. Each instance is distinguished by its position, and you reference the current position inside the block with count.index, a zero-based integer.

resource "aws_instance" "web" {
  count = 3

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = "web-${count.index}"
  }
}

This declares three EC2 instances tagged web-0, web-1, and web-2.

Output:

Terraform will perform the following actions:

  # aws_instance.web[0] will be created
  # aws_instance.web[1] will be created
  # aws_instance.web[2] will be created

Plan: 3 to add, 0 to change, 0 to destroy.

The list address: type.name[index]

When a resource uses count, its address becomes a list of instances. You access an individual instance by integer subscript, and the whole resource as a list (for example with the splat operator [*]).

# A single instance's private IP
output "first_ip" {
  value = aws_instance.web[0].private_ip
}

# Every instance's private IP, as a list
output "all_ips" {
  value = aws_instance.web[*].private_ip
}

You can also drive count from a list and index into that list with count.index, keeping per-instance values aligned:

variable "subnet_cidrs" {
  type    = list(string)
  default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

resource "aws_subnet" "private" {
  count             = length(var.subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.subnet_cidrs[count.index]
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "private-${count.index}"
  }
}

Because instances are identified by position, removing an item from the middle of a count-driven list shifts every later index down by one. Terraform then plans to destroy and recreate the trailing instances even though they did not really change. For collections keyed by a stable identifier, prefer for_each.

Conditional creation

A common pattern uses the ternary operator to turn a resource on or off. When count = 0 the resource is created zero times—effectively absent from the plan—and when count = 1 exactly one instance exists.

variable "enable_monitoring" {
  type    = bool
  default = false
}

resource "aws_cloudwatch_metric_alarm" "cpu_high" {
  count = var.enable_monitoring ? 1 : 0

  alarm_name          = "high-cpu"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = 120
  statistic           = "Average"
  threshold           = 80
}

Note that even a conditional resource is still a list. To reference it elsewhere you must use index [0], and you should guard against the disabled case:

output "alarm_arn" {
  value = var.enable_monitoring ? aws_cloudwatch_metric_alarm.cpu_high[0].arn : null
}

count vs for_each

Both meta-arguments create multiple instances, but they identify those instances differently. count keys by integer position; for_each keys by a map key or set member. That single difference drives the trade-offs below.

Aspectcountfor_each
AcceptsA whole numberA map or set of strings
Instance keyInteger index ([0], [1])Stable string key (["us-east-1a"])
Effect of reordering inputRecreates shifted instancesNo effect; keys are stable
Best forIdentical copies, on/off togglesDistinct objects with meaningful names
Reference syntaxaws_x.y[0], aws_x.y[*]aws_x.y["key"], values(aws_x.y)

Reach for count when the instances are truly interchangeable (a fixed pool of identical workers) or when you need the condition ? 1 : 0 toggle. Reach for for_each whenever each instance has a meaningful, stable identity—because removing or adding one item won’t churn the rest of the fleet.

You cannot use count and for_each on the same resource block. Choose one per resource.

Best Practices

  • Use count for genuinely identical instances and the condition ? 1 : 0 enable/disable idiom; reach for for_each when each instance has a stable identity.
  • Drive count from length(...) of an ordered list rather than a hard-coded number so scaling is data-driven.
  • Avoid removing items from the middle of a count-backed list—append or use for_each to prevent needless recreation.
  • Always index conditional resources as [0] and guard references with a ternary so plans don’t fail when the resource is disabled.
  • Expose collections with the splat operator (resource[*].attr) for clean, list-valued outputs.
  • Keep per-instance data structures (CIDRs, AZs, names) in aligned lists indexed by count.index to avoid drift between values.
Last updated June 14, 2026
Was this helpful?