DevOpsLesson
DevOpsLesson

Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.

Stay Updated

Get notified about new tutorials and features.

Tutorials

  • What is DevOps?
  • Docker Tutorial
  • Terraform Tutorial
  • CI/CD Pipeline
  • All Tutorials

Roadmaps

  • DevOps Engineer
  • Cloud Engineer
  • SRE Path
  • All Roadmaps

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 DevOpsLesson. All rights reserved.

DOCKERKUBERNETESTERRAFORMAWSCI/CDLINUXGITDEVOPS ROADMAPCLOUD ROADMAPSRE ROADMAPGIT CHEATSHEETDOCKER CHEATSHEETK8S CHEATSHEETTF CHEATSHEETLINUX CHEATSHEETDOCKERFILE LINTERYAML VALIDATORCRON PARSERREGEX TESTER

Terraform Tutorial

Introduction to Terraform
Installing Terraform
The Terraform Core Workflow
Terraform Variables and Outputs
Terraform State and Backends
Terraform Modules
Terraform Data Sources
Terraform Count and for_each
Terraform Expressions and Functions
Terraform Dynamic Blocks
Terraform Workspaces
Terraform with AWS
Terraform Provisioners
Terraform CI/CD Pipeline
Terraform Security

Terraform Count

PreviousPrev
Next

Learn how Terraform count works, how count.index is used, how to create conditional resources, how to reference counted resources, and when count is a good fit.

What Is the count Meta-Argument?

The count meta-argument lets Terraform create multiple instances from a single resource block. Instead of copy-pasting the same EC2 instance, subnet, or IAM user block several times, you define one block and tell Terraform how many copies to make.

At its simplest, count looks like this:

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

Terraform expands that one block into three instances:

  • aws_instance.web[0]
  • aws_instance.web[1]
  • aws_instance.web[2]

That indexing behavior is the foundation of count. Every instance is identified by a numeric position starting at zero.

count is called a meta-argument because it is not specific to one resource type. Many resources and modules can use it. It changes how Terraform interprets the block rather than configuring the cloud provider object itself.

Why count Is Useful

Without count, repeating resources means repeating code. That creates several problems:

  • more typing
  • more chances for inconsistency
  • harder updates later
  • poor scalability as infrastructure grows

With count, you keep the declaration compact and let Terraform handle repetition.

A simple example is creating multiple nearly identical instances:

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

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

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

Now the number of instances can be changed by adjusting a variable instead of rewriting resource blocks.

How count Works Internally

When Terraform sees count, it evaluates the expression to a whole number. If the result is 0, no instances are created. If the result is 1, exactly one instance exists. If the result is 5, Terraform creates five instances and assigns them indexes from 0 to 4.

That means a counted resource is no longer referenced like a single object. Even if count = 1, Terraform still treats it as a collection of instances. You must reference it with an index.

For example:

resource "aws_instance" "web" {
  count         = 1
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
}

The correct reference is:

aws_instance.web[0].id

Not:

aws_instance.web.id

That is an important beginner detail.

Using count.index

Inside a block that uses count, Terraform gives you access to a special value called count.index.

count.index is the zero-based number of the current instance being created.

Example:

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

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

This produces names like:

  • web-0
  • web-1
  • web-2

If you want human-friendly numbering, add one:

Name = "web-${count.index + 1}"

That produces:

  • web-1
  • web-2
  • web-3

Common uses of count.index

  • naming repeated resources
  • selecting a value from a list
  • aligning related resources by position
  • generating per-instance tags or labels

The key idea is that count.index gives each repeated instance a way to know which copy it is.

Using count with Lists

One of the most common beginner patterns is combining count with a list variable.

Imagine you want to create one subnet per CIDR block:

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" "public" {
  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 = "public-subnet-${count.index + 1}"
  }
}

Here is what happens:

  • length(var.subnet_cidrs) decides how many subnets to create
  • count.index selects the matching CIDR block from the list
  • the same index is used to select an availability zone

This pattern is powerful because it lets you drive repeated infrastructure from structured inputs.

Why list alignment matters

When you use count with multiple lists, those lists must stay aligned by position. If the second subnet CIDR belongs in the second availability zone, your lists must stay in sync.

That can work well for small and stable configurations, but it becomes fragile if items are inserted, reordered, or removed later. This is one reason for_each is often preferred for named objects.

Creating Multiple Resources from a Variable

A common beginner pattern is to expose the number of resources through an input variable:

variable "instance_count" {
  description = "Number of application instances"
  type        = number
  default     = 2
}

resource "aws_instance" "app" {
  count         = var.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = {
    Name = "app-${count.index + 1}"
  }
}

This is useful when different environments need different sizes:

  • dev may use 1
  • staging may use 2
  • production may use 4

The same Terraform code can handle all of them.

Conditional Resource Creation with count

Another very common use of count is conditional creation. Because count can be 0 or 1, it becomes a simple feature toggle.

Example:

variable "create_instance" {
  type    = bool
  default = true
}

resource "aws_instance" "web" {
  count         = var.create_instance ? 1 : 0
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}

If var.create_instance is true, Terraform creates one instance. If it is false, Terraform creates none.

This is especially useful for:

  • optional bastion hosts
  • optional monitoring resources
  • environment-specific infrastructure
  • learning examples where a feature can be toggled on and off

Why the ternary expression works well here

Terraform needs count to evaluate to a number. A boolean alone is not enough. The ternary expression converts the condition into either 1 or 0.

count = var.create_instance ? 1 : 0

That small pattern appears in many real-world Terraform codebases.

Referencing Counted Resources

Once count is used, the resource becomes a list-like collection of instances. You must reference specific elements by index or use splat syntax for all instances.

Referencing a single counted resource

aws_instance.web[0].id

This is the first instance.

Referencing all instance IDs

aws_instance.web[*].id

This returns a list of IDs from every counted instance.

Example output

output "web_instance_ids" {
  value = aws_instance.web[*].id
}

If count = 3, the output is a list containing three instance IDs.

Referencing a conditionally created resource

Conditional resources need extra care. If count = 0, there is no [0] element.

That means this is only safe when the resource exists:

aws_instance.web[0].id

In real modules, you often protect optional references using expressions, conditionals, or outputs that return null when nothing exists.

For example:

output "web_instance_id" {
  value = var.create_instance ? aws_instance.web[0].id : null
}

This avoids invalid index errors.

count with Modules

count is not limited to resources. You can also use it on modules.

module "app_server" {
  count  = 2
  source = "./modules/ec2-instance"

  instance_name = "app-${count.index + 1}"
}

That tells Terraform to create two module instances:

  • module.app_server[0]
  • module.app_server[1]

This can be useful, but the same indexing tradeoffs still apply. If stable identities matter, for_each is usually a better long-term fit.

The Big Problem with count: List Ordering

count is simple, but it has a major downside: instance identity is tied to position, not meaning.

Suppose you create resources from this list:

variable "names" {
  default = ["app", "api", "worker"]
}

resource "aws_instance" "service" {
  count         = length(var.names)
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = {
    Name = var.names[count.index]
  }
}

Terraform maps them like this:

  • [0] -> app
  • [1] -> api
  • [2] -> worker

Now imagine you remove the middle item and change the list to:

["app", "worker"]

Terraform now sees:

  • [0] -> app
  • [1] -> worker

The old [1] used to be api, but now [1] means worker. Terraform may destroy and recreate resources after the removed element because the indexes shifted.

This is the classic count-ordering problem.

Why this matters operationally

If a list represents real infrastructure identities, removing one item from the middle can cause unnecessary replacement of later items. That can mean:

  • downtime
  • changed resource names
  • changed IDs
  • unnecessary churn in plans

That is why count works best when order is stable or identity does not matter very much.

When count Works Well

count is a strong choice when:

  • you need a simple number of identical resources
  • instances are naturally index-based
  • order is stable and not expected to change much
  • you want to enable or disable one resource with 0 or 1
  • the repeated resources are disposable or low-risk

Examples:

  • create 3 identical test instances
  • create one optional NAT gateway in a learning environment
  • create one subnet per fixed CIDR list in a small example
  • create N EBS volumes for a lab setup

count shines when the repetition problem is fundamentally numeric.

When for_each Is Better

for_each is usually a better choice when:

  • each instance has a meaningful name or key
  • the collection may grow or shrink over time
  • order should not determine identity
  • you want stable references like resource["prod"]
  • each item has its own settings

If you are creating resources for dev, staging, and prod, or for named buckets, teams, or applications, for_each usually models that better than numeric indexes.

Practical Comparison: count vs for_each

Questioncountfor_each
How are instances identified?By numeric indexBy map key or set member
Best forRepeating N similar itemsNamed or distinct items
Sensitive to reordering?YesMuch less
Simple conditional creation?Very easy with 0 or 1Possible, but less direct
Readability of referencesLower for large collectionsHigher when keys are meaningful

A simple rule of thumb is:

  • use count when the answer is a number
  • use for_each when the answer is a collection of named things

Best Practices for Using count

1. Keep list ordering stable

If your design depends on list position, document that clearly and avoid unnecessary reordering.

2. Use count.index only where it adds value

It is helpful for selecting list elements or building names, but if you are forcing everything through numeric indexes, that may be a sign that for_each is a better model.

3. Be careful with optional resources

Whenever count can become 0, protect downstream references so Terraform does not try to access [0] when nothing exists.

4. Prefer meaningful outputs

If your configuration creates multiple instances, output lists clearly:

output "instance_ids" {
  value = aws_instance.app[*].id
}

5. Do not fight the identity model

If order changes often, do not keep patching around count. Switch to for_each instead.

Common Beginner Mistakes

Mistake 1: Forgetting indexes when referencing counted resources

Once count is present, references need [index] or [*] syntax.

Mistake 2: Assuming count.index starts at 1

It starts at 0. Add one only for display-friendly names.

Mistake 3: Using count for named business objects

If items are naturally identified by names like prod, blue, or payments, for_each is usually the better design.

Mistake 4: Ignoring reordering risk

The biggest conceptual trap with count is believing indexes are stable identities. They are not. They are just positions.

Final Thoughts

Terraform count is one of the easiest ways to reduce repetition. It helps you create multiple similar resources, work through lists with count.index, and toggle resources on or off with simple conditionals.

Its biggest strength is also its biggest limitation: it is based on numeric positions. That makes it great for straightforward repetition, but weaker for collections where each item has its own durable identity.

If you remember these three points, you will use count well:

  • count creates N instances from one block
  • count.index gives you the current zero-based index
  • list reordering can cause unwanted resource replacement

Use it when the problem is numeric. Reach for for_each when the problem is really about named items.


Knowledge Check

Exercise

Question 1: count.index

What does count.index represent inside a resource that uses count?

Exercise

Question 2: Conditional Resources

Which expression is a common Terraform pattern for creating a resource only when var.create_instance is true?

Exercise

Question 3: Ordering Risk

Why can removing a middle element from a list used with count cause unexpected resource replacement?

PreviousPrev
Next

Continue Learning

Writing Terraform Modules

Learn how to write reusable Terraform modules, structure them with main.tf, variables.tf, and outputs.tf, and publish or share modules across projects.

14 min read·Medium

Terraform Data Sources

Learn how Terraform data sources read existing infrastructure, how data blocks work, how outputs are referenced, and when to use data sources instead of resources.

12 min read·Easy

Terraform Count and for_each

Get a quick overview of Terraform count and for_each so you can choose the right repetition pattern before learning each meta-argument in detail.

5 min read·Easy

Explore Related Topics

AW

AWS Tutorials

Provision AWS infrastructure with Terraform

CI

CI/CD Tutorials

Run terraform plan/apply in CI pipelines

Try the Tool

YAML Validator

Validate Terraform and Kubernetes YAML configs before applying.

On This Page

What Is the `count` Meta-Argument?Why `count` Is UsefulHow `count` Works InternallyUsing `count.index`Common uses of `count.index`Using `count` with ListsWhy list alignment mattersCreating Multiple Resources from a VariableConditional Resource Creation with `count`Why the ternary expression works well hereReferencing Counted ResourcesReferencing a single counted resourceReferencing all instance IDsExample outputReferencing a conditionally created resource`count` with ModulesThe Big Problem with `count`: List OrderingWhy this matters operationallyWhen `count` Works WellWhen `for_each` Is BetterPractical Comparison: `count` vs `for_each`Best Practices for Using `count`1. Keep list ordering stable2. Use `count.index` only where it adds value3. Be careful with optional resources4. Prefer meaningful outputs5. Do not fight the identity modelCommon Beginner MistakesMistake 1: Forgetting indexes when referencing counted resourcesMistake 2: Assuming `count.index` starts at 1Mistake 3: Using `count` for named business objectsMistake 4: Ignoring reordering riskFinal ThoughtsKnowledge Check