WintelGuy.com

Terraform count Explained with Practical Examples

Contents

Introduction

Terraform is a powerful Infrastructure as Code (IaC) tool that allows you to define and provision infrastructure resources using configuration files.

In this article we will review Terraform's ability to dynamically create multiple instances of a resource using the count meta-argument.

We'll begin with a simple example, such as creating multiple similar Google Cloud Storage (GCS) buckets, and progress to more complex scenarios, including dynamically creating resources from input objects. By the end of this tutorial, you'll understand not just how count works, but when and why to use it to simplify and scale your configurations.

To follow along you need a basic understanding of Terraform syntax and have the following set up:

  • A Google Cloud account (you can use the free tier for this tutorial).
  • A Google Cloud Platform (GCP) project with billing enabled.
  • A computer with the Google Cloud SDK (gcloud) installed and initialized, and Terraform CLI installed. Alternatively, you can use GCP Cloud Shell.
  • Terraform authenticated with GCP, either by:
    • Running gcloud auth application-default login, or
    • Setting up a GCP service account and downloading its key for use with Terraform.

Once you have the tools above ready, you're all set to explore how count can improve your Terraform configurations!

Back to Top

Getting Started with count

Terraform's count meta-argument is one of the simplest yet highly effective tools in your IaC toolkit. It lets you specify (without duplicating the resource code blocks) how many copies of a given resource or module Terraform should create.

Basic count syntax:

resource "RESOURCE_TYPE" "NAME" { count = <expression> # an integer value name = "my-widget-${count.index}" ... }

If count is present in a resource block and greater than 0, Terraform will create the specified number of instances of that resource. The result is a list of resources referenced by the name RESOURCE_TYPE.NAME, not individual named resources. You can access individual objects in the list by providing the index value - RESOURCE_TYPE.NAME[index].
Note: Terraform uses zero-based indexing, so the first instance is always RESOURCE_TYPE.NAME[0].

For example, if count = 3, Terraform will create three instances of the resource: RESOURCE_TYPE.NAME[0], RESOURCE_TYPE.NAME[1], and RESOURCE_TYPE.NAME[2].

When you set count = 1, Terraform will create exactly one instance - RESOURCE_TYPE.NAME[0].

If count = 0, Terraform will not create any instances. Terraform ignores any resource block with count = 0 when planning/applying, as if the resource block doesn't exist at all. If a resource was previously created with count = 1 (or more), and then you change the configuration to count = 0, Terraform will destroy all existing resource instances.

Referencing Individual Instances with count.index:

When you use count, Terraform gives you access to a special variable called count.index. This represents the index (starting from 0) of the current resource instance being created. count.index is commonly used to define individual resource names, labels, or configuration settings.

Why Use count?

The main advantages of using count include:

  • Simplifying repetitive resource definitions: Instead of writing out multiple copies of a resource block, use count to define it once and create multiple instances.
  • Dynamic scaling: Easily adjust the number of resources by changing a single number.
  • Conditional creation: Set count = 1 or count = 0 to enable or disable a resource based on a variable.

Back to Top

Example 1: Creating Multiple Storage Buckets

Let's start with a simple example that demonstrates how to use the count meta-argument to create multiple Google Cloud Storage buckets.

Goal: We'll create three Cloud Storage buckets, each with a unique name, using a single resource block with the count meta-argument.

Terraform Code:

# Provider Configuration provider "google" { project = var.project_id } # Input Variable Definition variable "project_id" { description = "Your GCP project ID" type = string } # Resource Definition Using count resource "google_storage_bucket" "example" { count = 3 name = "tf-demo-bucket-${var.project_id}-${count.index}" location = "US" # delete bucket and content on destroy. force_destroy = true } # Output: Show Bucket Names output "all_bucket_names" { value = [ for bucket in google_storage_bucket.example : bucket.name ] }

How It Works:

  • var.project_id - this variable should be set to your actual GCP project ID. You can define it directly in the file or pass it as a CLI parameter.
  • count = 3 tells Terraform to create three buckets.
  • ${count.index} - this is the index number (0 to 2) for each bucket. We use it to make each bucket name unique.
  • force_destroy = true allows you to delete a bucket that contains objects (to simplify cleanup process).
  • for expression iterates over the resource list google_storage_bucket.example to retrieve bucket names.

Important: Bucket names must be globally unique across all of Google Cloud. Including the project ID and an index helps avoid naming conflicts.

How to Apply This Configuration:

  • Save the code to a file named main.tf
  • Set your project ID: export TF_VAR_project_id=YOUR_PROJECT_ID
  • Initialize the Terraform working directory: terraform init
  • Run: terraform apply

Terraform will create three buckets with the following names:

  • tf-demo-bucket-YOUR_PROJECT_ID-0
  • tf-demo-bucket-YOUR_PROJECT_ID-1
  • tf-demo-bucket-YOUR_PROJECT_ID-2

Individual buckets can be referenced by using the indexes of the google_storage_bucket.example list. For example, to get bucket names use:

  • First bucket: google_storage_bucket.example[0].name
  • Second bucket: google_storage_bucket.example[1].name
  • Third bucket: google_storage_bucket.example[2].name

Cleaning Up:

To delete the buckets, simply run terraform destroy. Confirm when prompted, and Terraform will delete all the created resources.

Summary:

In this example, we illustrated how to:

  • Use count to create multiple resources
  • Reference each instance within the resource block with count.index
  • Access created resource instances using list index - RESOURCE_TYPE.NAME[index]
  • Iterate over the list of resources with for

Back to Top

Example 2: Conditional Resource Creation

In some cases resources need to be created only under certain conditions. We can utilize count meta-argument to implement such if-then logic in Terraform code.

Goal: Let's say we want to create a Cloud Storage bucket only if the var.create_bucket variable is true.

Terraform Code:

# Provider Configuration provider "google" { project = var.project_id } # Input Variables variable "project_id" { description = "Your GCP project ID" type = string } variable "create_bucket" { description = "Set to true to create the optional bucket" type = bool default = false } # Resource Definition resource "google_storage_bucket" "optional" { count = (var.create_bucket ? 1 : 0) name = "tf-optional-bucket-${var.project_id}" location = "US" # delete bucket and content on destroy. force_destroy = true } # Output: Show Bucket Name output "optional_bucket_name" { value = (var.create_bucket ? google_storage_bucket.optional[0].name : null) }

How It Works:

  • count = (var.enable_bucket ? 1 : 0) - this is the key line. It evaluates the boolean variable var.enable_bucket:
    • If true, it sets count to 1 and google_storage_bucket.optional[0] is created.
    • If false, count is 0 and no resource is created.
  • The output block also checks the condition (var.create_bucket ?) to avoid referencing a non-existent resource and prevent runtime errors.
  • force_destroy = true allows you to delete a bucket that contains objects (to simplify cleanup).

How to Apply This Configuration:

  • Save the code to a file named main.tf
  • Set the create_bucket variable to true or false as desired
  • Set your project ID: export TF_VAR_project_id=YOUR_PROJECT_ID
  • Initialize the Terraform working directory: terraform init
  • Run: terraform apply

If enable_bucket is true, the bucket will be created. You'll see the bucket name in the output only if it exists.

If enable_bucket is false, nothing is created, and no error occurs.

If a resource was previously created with count = 1, and then you change the configuration to set count = 0, Terraform will:

  • Destroy the existing bucket
  • Remove it from the state after the apply

Cleaning Up:

To delete the buckets, simply run terraform destroy. Confirm when prompted, and Terraform will delete all the created resources.

Summary:

Using count for conditional resource creation is:

  • Ideal for toggling optional infrastructure features
  • Safe when combined with conditional expressions for outputs or references

Back to Top

Example 3: Resource Creation from a List Input

In many Terraform projects, you'll want to create multiple resources with specific parameters based on input values, such as a list of names, locations, etc. Terraform's count meta-argument, combined with list input variables, enables this dynamic behavior.

Goal: We want to create multiple Google Cloud Storage buckets, each with a custom name from a list defined in an input variable.

Terraform Code:

# Provider Configuration provider "google" { project = var.project_id } # Input Variables variable "project_id" { description = "Your GCP project ID" type = string } variable "bucket_names" { description = "List of GCS bucket names to create" type = list(string) default = ["logs", "backups", "assets"] } # Resource Definition Using count resource "google_storage_bucket" "multi" { count = length(var.bucket_names) name = "${var.bucket_names[count.index]}-${var.project_id}" location = "US" # delete bucket and content on destroy. force_destroy = true } # Output: Bucket URLs output "bucket_urls" { value = [ for bucket in google_storage_bucket.multi : bucket.url ] }

How It Works:

  • count = length(var.bucket_names) determines how many buckets to create based on the number of names provided in the bucket_names list.
  • var.bucket_names[count.index] retrieves the name for each resource being created.
  • ${...}-${var.project_id} appends the project ID to ensure globally unique bucket names.
  • force_destroy = true allows you to delete a bucket that contains objects (to simplify cleanup process).
  • The output uses a for expression to iterate over the list of created buckets google_storage_bucket.multi and return bucket URLs.

How to Apply This Configuration:

  • Save the code to a file named main.tf
  • Set your project ID: export TF_VAR_project_id=YOUR_PROJECT_ID
  • Initialize the Terraform working directory: terraform init
  • Run: terraform apply

After applying, you'll see the list of bucket URLs in the output, e.g.:

bucket_urls = [ "gs://logs-YOUR_PROJECT_ID", "gs://backups-YOUR_PROJECT_ID", "gs://assets-YOUR_PROJECT_ID" ]

To access individual bucket attributes in expressions or outputs, use index-based reference, for example:

  • google_storage_bucket.multi[0].name
  • google_storage_bucket.multi[1].location

Apply this approach only when you're confident about the list size and order.

To illustrate how changes to the input list may affect the existing objects, let's remove the "backup" name from the bucket_names list and re-run terraform apply. Since there are now only two names on the list - ["logs", "assets"], Terraform detects the changes in the configuration and performs the following actions:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - destroy -/+ destroy and then create replacement Terraform will perform the following actions: # google_storage_bucket.multi[1] must be replaced -/+ resource "google_storage_bucket" "multi" { - default_event_based_hold = false -> null - enable_object_retention = false -> null ~ id = "backups-YOUR_PROJECT_ID" -> (known after apply) - labels = {} -> null ~ name = "backups-YOUR_PROJECT_ID" -> "assets-YOUR_PROJECT_ID" # forces replacement ... # google_storage_bucket.multi[2] will be destroyed # (because index [2] is out of range for count) - resource "google_storage_bucket" "multi" { ... Plan: 1 to add, 0 to change, 2 to destroy. Changes to Outputs: ~ bucket_urls = [ "gs://logs-YOUR_PROJECT_ID", - "gs://backups-YOUR_PROJECT_ID", - "gs://assets-YOUR_PROJECT_ID", + (known after apply), ] ... google_storage_bucket.multi[1]: Destroying... [id=backups-YOUR_PROJECT_ID] google_storage_bucket.multi[2]: Destroying... [id=assets-YOUR_PROJECT_ID] google_storage_bucket.multi[2]: Destruction complete after 1s google_storage_bucket.multi[1]: Destruction complete after 1s google_storage_bucket.multi[1]: Creating... google_storage_bucket.multi[1]: Creation complete after 1s [id=assets-YOUR_PROJECT_ID] Apply complete! Resources: 1 added, 0 changed, 2 destroyed. Outputs: bucket_urls = [ "gs://logs-YOUR_PROJECT_ID", "gs://assets-YOUR_PROJECT_ID", ]

As you can se from the output, Terraform destroyed two buckets - backups-YOUR_PROJECT_ID and assets-YOUR_PROJECT_ID and then re-created the "assets" bucket.

Cleaning Up:

To delete the buckets, simply run terraform destroy. Confirm when prompted, and Terraform will delete all the created resources.

Summary:

In this example, we illustrated:

  • How to use count meta-argument together with a list input variable to dynamically create resources with custom attribute values
  • How indexing (count.index) maps input values to each resource

While this approach provides a simple and convenient way of creating multiple resources, it may produce unexpected results when you modify the values or the number of items on the list. Always review the terraform plan output to make sure the results match your expectations.

Back to Top

Example 4: Using count with Complex Object Input

In a situation when you need to create multiple similar objects and supply more than one input value per resource - such as name, location, or labels, you can combine count meta-argument with an object type input variable. In this example we'll use a list of maps as an input variable to drive such creation.

Goal: We need to create several Google Cloud Storage buckets, each with different:

  • Name
  • Location
  • Storage class

Terraform Code:

# Provider Configuration provider "google" { project = var.project_id } # Input Variable Definition variable "project_id" { description = "Your GCP project ID" type = string } variable "buckets" { description = "List of bucket definitions" type = list(object({ name = string location = string storage_class = string })) default = [ { name = "data" location = "US-EAST1" storage_class = "STANDARD" }, { name = "backup" location = "US-WEST1" storage_class = "NEARLINE" }, { name = "archive" location = "US" storage_class = "COLDLINE" } ] } # Resource Definition Using count resource "google_storage_bucket" "multi" { count = length(var.buckets) name = "${var.buckets[count.index].name}-${var.project_id}" location = var.buckets[count.index].location storage_class = var.buckets[count.index].storage_class force_destroy = true } # Output: Show Bucket Parameters output "created_buckets" { value = [ for bucket in google_storage_bucket.multi : { name = bucket.name location = bucket.location storage_class = bucket.storage_class } ] }

How It Works:

  • var.buckets contains a list of objects (maps), each defining individual bucket properties.
  • count = length(var.buckets) determines how many buckets to create based on the number of objects in var.buckets.
  • var.buckets[count.index] selects the corresponding parameter object for each resource instance.
  • force_destroy = true allows you to delete a bucket that contains objects (to simplify cleanup process).
  • The output block returns configuration details for created buckets.

How to Apply This Configuration:

  • Save the code to a file named main.tf
  • Set your project ID: export TF_VAR_project_id=YOUR_PROJECT_ID
  • Initialize the Terraform working directory: terraform init
  • Run: terraform apply

After applying, Terraform creates 3 GCS buckets, each with unique settings:

Outputs: created_buckets = [ { location = "US-EAST1" name = "data-YOUR_PROJECT_ID" storage_class = "STANDARD" }, { location = "US-WEST1" name = "backup-YOUR_PROJECT_ID" storage_class = "NEARLINE" }, { location = "US" name = "archive-YOUR_PROJECT_ID" storage_class = "COLDLINE" }, ]

You can access a specific resource from the list by using index: google_storage_bucket.multi[0].url, or by filtering output dynamically:

output "archive_bucket" { value = [ for bucket in google_storage_bucket.multi : bucket.name if bucket.storage_class == "COLDLINE" ] }

Cleaning Up:

To delete the buckets, simply run terraform destroy. Confirm when prompted, and Terraform will delete all the created resources.

Summary:

  • Use a list of objects when each resource instance requires custom configuration.
  • Access specific values inside input objects using var.<variable>[count.index].<attribute>.
  • Combine outputs and conditionals to get access to specific resources.

Back to Top

Limitations and Caveats

While count meta-argument is a powerful tool in Terraform, it comes with certain limitations and behavioral caveats that can lead to unexpected results if not properly understood. Here are key points to be aware of when using count.

Index-Based Access

When using count, resources are created as a list, and each instance is accessed using an index (resource_name[0], resource_name[1], etc.).

Changes in Count Value Can Trigger Resource Recreation

Altering the value of count or changing the length of the input list may destroy one or more instances. This can cause side effects such as:

  • Data loss
  • Re-creation of dependent resources

Conditional Logic Requires Careful Output Handling

Referencing resource_name[0] when count = 0 (no resources were created) will result in a Terraform error. Use conditional expressions to avoid referencing a resource that doesn't exist.

Back to Top

Best Practices

Use count for simple repetition when:

  • All resources are similar or interchangeable
  • Index-based access logic is acceptable
  • You don't need stable, named resources

Utilize count with conditional expressions for optional resources:

  • use count = condition ? 1 : 0 and combine with conditional outputs to safely handle optional resource

Avoid hard-coding indices

  • Where possible, avoid referencing hard-coded indices (e.g., resource_name[1]) unless the position in the list is guaranteed to remain constant. Use looping expressions like for or dynamic outputs instead.

Prefer for_each for complex or named resources when

  • You want predictable and stable addressing
  • You're using maps or sets of strings as input
  • Each resource has a unique identity or key

Back to Top

Conclusion

Terraform's count meta-argument enables you to efficiently create and manage multiple instances of a resource using simple logic or dynamic input. From basic repetition to conditional and data-driven infrastructure, count helps reduce code duplication and streamline your configurations.

While it's a great tool for many scenarios, it's important to understand its limitations and apply best practice, especially when handling optional resources or dynamic lists.

With thoughtful use, count can significantly enhance the flexibility and scalability of your Terraform projects.

Understanding the nuances of count is key to writing safe, scalable Terraform code. When used appropriately, it enables powerful resource automation. When misused, it can lead to fragile infrastructure and hard-to-diagnose issues.

See Also:
Handling Terraform State in Multi-Environment Deployments
Understanding Terraform Variable Precedence
Terraform Value Types Tutorial
Terraform for_each Tutorial with Practical Examples
Exploring Terraform dynamic Blocks with GCP Examples
Working with External Data in Terraform
Handling Sensitive and Ephemeral Data in Terraform
Terraform Modules FAQ