Terraform Count Parameter
Introduction
When building infrastructure as code with Terraform, you'll often need to create multiple copies of the same resource—such as spinning up several identical EC2 instances or creating multiple IAM users with similar permissions. Instead of duplicating code blocks, Terraform provides a powerful feature called the count parameter that allows you to create multiple instances of a resource from a single resource block.
The count parameter is one of Terraform's meta-arguments—special arguments accepted by any resource block, regardless of the resource type. It enables you to specify how many copies of the resource should be created, making your code more concise, maintainable, and programmatic.
Basic Syntax
The basic syntax for using the count parameter is straightforward:
resource "aws_instance" "server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "server-${count.index}"
}
}
In this example:
count = 3
tells Terraform to create three identical EC2 instancescount.index
is a special value that gives you the current index (0, 1, 2) for each instance- The instances will be named "server-0", "server-1", and "server-2" using string interpolation
How Count Works
When you specify a count value, Terraform creates that many instances of the resource. Each instance gets a unique index starting from 0 through count.index
. This index can be used within the resource block to create unique values for each instance.
Here's what happens behind the scenes:
Accessing Count Instances
When using count, the resource address changes from a single resource to a list of resources. This affects how you reference these resources in your Terraform configuration:
output "instance_ids" {
value = aws_instance.server[*].id
}
output "first_instance_ip" {
value = aws_instance.server[0].private_ip
}
Key access patterns:
- Use
[*]
to get a list of all attributes across all instances - Use
[INDEX]
to access a specific instance by its index
Practical Examples
Example 1: Creating Multiple S3 Buckets
Let's create multiple S3 buckets with different names:
variable "bucket_names" {
description = "Names of S3 buckets to create"
type = list(string)
default = ["logs-bucket", "assets-bucket", "backups-bucket"]
}
resource "aws_s3_bucket" "buckets" {
count = length(var.bucket_names)
bucket = "${var.bucket_names[count.index]}-${terraform.workspace}"
acl = "private"
tags = {
Name = var.bucket_names[count.index]
Environment = terraform.workspace
}
}
In this example:
- We define a list of bucket names in a variable
count = length(var.bucket_names)
creates as many buckets as there are names in our list- We use
count.index
to access the corresponding name from our list
Example 2: Conditional Resource Creation
The count parameter can be used to conditionally create resources by using a boolean expression:
variable "create_database" {
description = "Whether to create the database"
type = bool
default = true
}
resource "aws_db_instance" "database" {
count = var.create_database ? 1 : 0
allocated_storage = 20
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t2.micro"
name = "mydb"
username = "admin"
password = "password"
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
}
Here:
- If
create_database
is true, count equals 1 and the resource is created - If
create_database
is false, count equals 0 and the resource is not created - This allows for environment-specific configurations without modifying the code
Example 3: Creating Resources from a Map
You can use count with for_each
expressions to create resources from map data:
variable "users" {
description = "Map of users with their roles"
type = map(string)
default = {
"john" = "admin"
"mary" = "developer"
"david" = "analyst"
}
}
locals {
user_list = [for name, role in var.users : {
name = name
role = role
}]
}
resource "aws_iam_user" "team_members" {
count = length(local.user_list)
name = local.user_list[count.index].name
tags = {
Role = local.user_list[count.index].role
}
}
resource "aws_iam_user_policy_attachment" "user_permissions" {
count = length(local.user_list)
user = aws_iam_user.team_members[count.index].name
policy_arn = local.user_list[count.index].role == "admin" ? "arn:aws:iam::aws:policy/AdministratorAccess" : "arn:aws:iam::aws:policy/ReadOnlyAccess"
}
This example:
- Converts a map of users and roles into a list format
- Creates an IAM user for each entry in the list
- Assigns different policies based on the user's role
Count vs. For Each
While the count parameter is powerful, Terraform also offers another meta-argument called for_each
which is sometimes more appropriate:
Feature | Count | For Each |
---|---|---|
Data type | Numeric | Map or Set |
Index type | Numeric (0, 1, 2...) | Key-based |
Order | Ordered by index | Unordered |
Resource addressing | aws_instance.server[0] | aws_instance.server["key"] |
Use count when:
- You need exactly N identical resources with minor variations
- The resource instances are identified by their position/index
- You're conditionally creating a resource (0 or 1)
Limitations and Gotchas
- The "count index" trap:
When using count with a list of resources, removing an item from the middle of the list can cause unexpected resource recreation:
# Before: var.names = ["a", "b", "c"]
# After: var.names = ["a", "c"]
resource "aws_instance" "server" {
count = length(var.names)
# ... other configuration ...
tags = {
Name = var.names[count.index]
}
}
In this case, "b" is removed, so "c" shifts to index 1. Terraform will:
- Destroy the resource with Name="c" (index 2)
- Modify the resource with Name="b" (index 1) to have Name="c"
This can lead to unexpected resource replacements. For this use case, for_each
is usually better.
- Zero count resources:
If a resource has count = 0
, Terraform won't create it. This affects how you reference that resource:
resource "aws_instance" "optional" {
count = var.create_instance ? 1 : 0
# ... configuration ...
}
resource "aws_eip" "optional_ip" {
count = var.create_instance ? 1 : 0
instance = aws_instance.optional[0].id # This will error if count = 0!
}
To fix this, you need to ensure the dependent resource also has count = 0 when the first resource doesn't exist:
resource "aws_eip" "optional_ip" {
count = var.create_instance ? 1 : 0
instance = var.create_instance ? aws_instance.optional[0].id : null
}
Best Practices
-
Avoid count for non-identical resources:
- Use count for truly identical resources with minor variations
- For distinct resources that happen to use the same resource type, use separate resource blocks
-
Use locals for complex expressions:
hcllocals {
instance_count = var.environment == "production" ? var.prod_instance_count : var.dev_instance_count
}
resource "aws_instance" "app" {
count = local.instance_count
# ... configuration ...
} -
Consider for_each for maps and sets:
- When working with named resources, for_each often works better
- Count is better for purely numeric scaling (e.g., "give me 5 instances")
-
Always use zero-based indexing:
- Remember that count.index starts at 0
- Design your naming schemes accordingly (e.g., "app-1", "app-2" might need
"app-${count.index + 1}"
)
Summary
The Terraform count parameter is a powerful tool that allows you to create multiple instances of a resource from a single configuration block. Key takeaways:
- Use count when you need multiple identical resources with minor variations
- The special value
count.index
gives you the current iteration index (starting from 0) - Count can be used for conditional resource creation with boolean expressions
- For resources identified by names rather than numeric indices, consider for_each instead
- Be careful when removing or reordering items in lists used with count
Exercises
-
Create a Terraform configuration that provisions three S3 buckets with names "bucket-0", "bucket-1", and "bucket-2".
-
Write a configuration that conditionally creates a VPC and its associated subnets based on a boolean variable.
-
Create a configuration that deploys a different number of EC2 instances based on the workspace (e.g., 2 for development, 5 for production).
-
Challenge: Create a configuration that generates a security group with a variable number of ingress rules based on a list of port numbers.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)