Terraform becomes truly powerful when you start using meta-arguments — special parameters that change how resources are created, managed, and related to each other. While Terraform syntax is easy to learn, mastering meta-arguments like count, for_each, and depends_on is what enables you to build dynamic, scalable, and production-ready AWS infrastructure.
This guide explains each meta-argument in detail with clear examples, best practices, and common real-world scenarios where these features become essential.
Understanding Meta Arguments in Terraform
Meta-arguments are not specific to any resource type. Instead, they can be applied to any Terraform resource, module, or data source to control creation patterns and dependency behavior.
The three foundational meta-arguments are:
-
count→ create multiple instances of a resource using index numbers -
for_each→ create multiple resources using map or set keys (more predictable) -
depends_on→ explicitly define resource ordering and dependencies
Let’s explore each one in detail.
1. count — Create Resources Dynamically Using Indexing
count is the simplest way to create multiple instances of the same resource. It works by creating resources based on a number, and you use count.index to reference each instance.
Example — Creating Multiple S3 Buckets Using count
variable "bucket_names" {
type = list(string)
default = ["app-logs", "media-storage", "archive-backups"]
}
resource "aws_s3_bucket" "buckets" {
count = length(var.bucket_names)
bucket = var.bucket_names[count.index]
}
Terraform will create three buckets and name them based on list values. This is the simplest way to scale resource creation.
When to Use count
- When resources are identical
- When order doesn’t matter
- When your inputs are lists, not maps
- When index-based naming is acceptable
Limitations
The main drawback of count is index shifting.
If the order of your list changes, Terraform may destroy and recreate resources unnecessarily. For example, adding a new item in the middle of the list changes all subsequent indexes.
2. for_each — Create Resources with Stable Keys
for_each solves the indexing problem by using keys instead of positions. This ensures resources remain stable even when items are added or removed.
Example — Creating Buckets Using for_each
variable "buckets" {
type = map(string)
default = {
logs = "app-logs"
media = "media-storage"
backup = "archive-backups"
}
}
resource "aws_s3_bucket" "bucket" {
for_each = var.buckets
bucket = each.value
}
Here, resources are created using keys (logs, media, backup) instead of index numbers.
When to Use for_each
- When you want stable instances
- When input is a set or map
- When you need meaningful Terraform addresses like:
aws_s3_bucket.bucket["logs"]
Why for_each Is Better Than count
Unlike count, adding a new key does not recreate other resources. Keys remain stable indefinitely, making this the preferred method for production infrastructure.
3. depends_on — Explicitly Define Resource Dependencies
Terraform generally detects dependencies automatically. For example, if an EC2 instance references a subnet ID, Terraform understands the subnet must be created first.
However, sometimes you must explicitly declare dependencies — especially when:
- Provisioners are involved
- Resources don’t reference each other directly
- Modules need ordering
- Upstream actions must complete first
Example — Wait for S3 Bucket Before Running Local Script
resource "aws_s3_bucket" "example" {
bucket = "demo-meta-argument-bucket"
}
resource "null_resource" "notify" {
depends_on = [aws_s3_bucket.example]
provisioner "local-exec" {
command = "echo S3 bucket created!"
}
}
When to Use depends_on
- When implicit dependency cannot be detected
- When actions must occur in strict sequence
- When you must enforce ordering between modules
Best Practice
Use depends_on only when needed.
Terraform is good at understanding dependencies automatically, so avoid overusing it to prevent unnecessary serialization of your plan.
Putting It All Together — Real-World Example
Here’s a scenario combining all three meta-arguments:
variable "apps" {
type = map(string)
default = {
app1 = "app1-logs"
app2 = "app2-logs"
}
}
resource "aws_s3_bucket" "buckets" {
for_each = var.apps
bucket = each.value
}
resource "null_resource" "verify" {
count = length(var.apps)
depends_on = [aws_s3_bucket.buckets]
provisioner "local-exec" {
command = "echo Created bucket ${count.index}"
}
}
In this setup:
-
for_eachensures stable bucket creation -
countruns verification for each bucket -
depends_onensures verification runs after bucket creation
This pattern appears frequently in CI/CD pipelines, multi-environment deployments, and state validation workflows.
Conclusion
Meta-arguments like count, for_each, and depends_on are the backbone of dynamic Terraform configurations. They help you scale your AWS infrastructure safely, maintain predictable behavior, and enforce dependency flow.
If you want to grow as a Terraform engineer, understanding these concepts is essential. Use count for simple repetition, for_each for stable and meaningful keys, and depends_on when Terraform needs guidance on ordering.
Reference Video

Top comments (0)