count in Terraform
The count meta-argument creates multiple instances of a resource from a single block. It’s the simplest way to provision N identical (or nearly identical) resources — subnets across availability zones, IAM users from a list, replicas of a server.
Basic count Usage
# Create 3 identical EC2 instancesresource "aws_instance" "workers" { count = 3 ami = data.aws_ami.ubuntu.id instance_type = "t3.micro"
tags = { Name = "worker-${count.index}" # worker-0, worker-1, worker-2 }}count.index is the zero-based index of the current instance (0, 1, 2, …).
count.index for Unique Naming
# Subnets across availability zonesresource "aws_subnet" "public" { count = length(var.availability_zones) vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) availability_zone = var.availability_zones[count.index]
tags = { Name = "public-${var.availability_zones[count.index]}" Tier = "public" }}
# Reference by indexoutput "public_subnet_ids" { value = aws_subnet.public[*].id # All subnet IDs as a list}
# Access a specific instanceoutput "first_public_subnet_id" { value = aws_subnet.public[0].id}count from Variable
variable "instance_count" { type = number default = 2}
resource "aws_instance" "app" { count = var.instance_count ami = data.aws_ami.ubuntu.id instance_type = var.instance_type
tags = { Name = "${var.environment}-app-${count.index + 1}" # 1-based for display Index = count.index }}
# Scale to 0 to create no instances# var.instance_count = 0 → creates nothingcount from a List
variable "user_names" { type = list(string) default = ["alice", "bob", "charlie"]}
resource "aws_iam_user" "team" { count = length(var.user_names) name = var.user_names[count.index]
tags = { CreatedBy = "terraform" Index = count.index }}
output "user_arns" { value = aws_iam_user.team[*].arn # All ARNs as a list}Conditional Resource Creation
Set count = 0 to create no instances, count = 1 to create one. Useful for optionally creating a resource:
variable "enable_bastion" { type = bool default = false}
resource "aws_instance" "bastion" { count = var.enable_bastion ? 1 : 0 ami = data.aws_ami.ubuntu.id instance_type = "t3.micro"}
# Access the conditionally-created resourceoutput "bastion_ip" { value = var.enable_bastion ? aws_instance.bastion[0].public_ip : null}# Conditionally create an ElasticSearch domain in production onlyresource "aws_elasticsearch_domain" "search" { count = var.environment == "production" ? 1 : 0 domain_name = "search-production" elasticsearch_version = "8.10"}Splat Expressions: Accessing All Instances
# Collect all instance IDsoutput "instance_ids" { value = aws_instance.app[*].id}
# Pass all subnet IDs to a load balancerresource "aws_lb" "main" { subnets = aws_subnet.public[*].id}
# All security group IDsresource "aws_db_instance" "main" { vpc_security_group_ids = [aws_security_group.database[*].id]}count vs for_each: When to Use Which
# Use count when:# - Creating N identical or index-differentiated resources# - You only care about the position, not the identity
# Creating 5 identical workersresource "aws_instance" "workers" { count = 5 # ...}
# Use for_each when:# - Each instance has a unique, stable identity# - You want to reference instances by name, not index
variable "users" { type = set(string) default = ["alice", "bob", "charlie"]}
resource "aws_iam_user" "team" { for_each = var.users name = each.key # Stable identity: "alice", "bob", "charlie"}The critical count limitation: if you remove an element from the middle of a list, all subsequent indices shift — Terraform destroys and recreates resources unexpectedly.
# If users = ["alice", "bob", "charlie"] and you remove "alice":# count = 3 → count = 2# aws_iam_user.team[0] was "alice", becomes "bob"# aws_iam_user.team[1] was "bob", becomes "charlie"# aws_iam_user.team[2] ("charlie") is deleted# Result: Terraform destroys "bob" and "charlie" and recreates them as "bob" and "charlie"# But their history/permissions are lost during recreation
# With for_each: removing "alice" only deletes "alice" — "bob" and "charlie" are untouchedUse count for homogeneous resources where identity doesn’t matter (replicas, subnets). Use for_each when each instance is distinct.