Skip to content

Meta Arguments


Terraform resource blocks normally describe one resource, but real infrastructure often needs several similar objects. You could copy a resource block ten times, but Terraform provides meta-arguments that let one declaration describe multiple instances and control relationships between them.

The most important ones here are count, for_each, and depends_on.

What Are Meta-Arguments?

Meta-arguments are special arguments understood by Terraform itself rather than by the provider as ordinary resource attributes.

For example:

resource "some_provider_server" "web" {
  count = 3

  name = "web-${count.index}"
}

Here count is not an attribute that the provider’s server API necessarily understands. Terraform uses it to determine how many resource instances should exist.

The same idea applies to:

for_each
depends_on

These arguments affect how Terraform manages the resource.

Creating Instances with count

The simplest way to create several similar instances is count.

resource "some_provider_server" "web" {
  count = 3

  name = "web-${count.index}"
}

Terraform creates three resource instances:

some_provider_server.web[0]
some_provider_server.web[1]
some_provider_server.web[2]

count.index gives each instance a numeric index.

The names therefore become:

web-0
web-1
web-2

The important concept is that count does not create one resource containing three servers. It creates three resource instances.

count and Resource Addresses

Without count:

some_provider_server.web

With:

count = 3

Terraform has:

some_provider_server.web[0]
some_provider_server.web[1]
some_provider_server.web[2]

Those addresses become part of Terraform’s state identity.

This matters when you later change the collection or count.

Why Indexes Can Become a Problem

Suppose you use a list:

variable "servers" {
  default = [
    "frontend",
    "backend",
    "worker"
  ]
}

and:

resource "some_provider_server" "app" {
  count = length(var.servers)

  name = var.servers[count.index]
}

The instances are:

app[0] → frontend
app[1] → backend
app[2] → worker

Now remove backend:

variable "servers" {
  default = [
    "frontend",
    "worker"
  ]
}

The indexes become:

app[0] → frontend
app[1] → worker

Terraform sees app[1] as the existing instance address, but its desired value has changed from backend to worker.

That means an index-based model can cause an existing instance to be repurposed or replaced depending on which resource attributes changed.

The important issue is identity.

With count, identity is numeric:

0
1
2

That is sometimes exactly what you want, but not always.

for_each Gives Instances Keys

When each object has a meaningful identity, for_each is often a better fit.

For example:

variable "servers" {
  default = {
    frontend = "small"
    backend  = "medium"
    worker   = "small"
  }
}

resource "some_provider_server" "app" {
  for_each = var.servers

  name = each.key
  size = each.value
}

Terraform creates:

some_provider_server.app["frontend"]
some_provider_server.app["backend"]
some_provider_server.app["worker"]

Here the identity is the key.

frontend → small
backend  → medium
worker   → small

This is much closer to how we naturally think about named infrastructure.

each.key and each.value

Inside a for_each resource:

name = each.key
size = each.value

For:

{
  frontend = "small"
  backend  = "medium"
}

Terraform evaluates the instances as:

frontend → key=frontend, value=small
backend  → key=backend, value=medium

Sets with for_each

for_each can also work with a set of strings:

variable "servers" {
  default = [
    "frontend",
    "backend",
    "worker"
  ]
}

resource "some_provider_server" "app" {
  for_each = toset(var.servers)

  name = each.key
}

The important property is that each set element becomes an instance key.

You should not depend on list ordering when using a set.

count vs for_each

The practical distinction is:

countfor_each
Numeric instance identityKey-based instance identity
resource.name[0]resource.name["frontend"]
Good for identical or positional instancesGood for named/distinct instances
Collection changes can affect indexesKeys remain tied to their instances
Often simpler for fixed quantitiesOften safer for long-lived named resources

A useful rule is:

Use count when the number of instances is the important thing. Use for_each when the identity of each instance is important.

This is not an absolute rule, but it is a very useful default.

Changing a for_each Collection

Suppose you have:

for_each = {
  frontend = "small"
  backend  = "medium"
  worker   = "small"
}

and remove backend:

for_each = {
  frontend = "small"
  worker   = "small"
}

Terraform sees:

frontend → still exists
worker   → still exists
backend  → removed

The other keys do not shift.

That is the major identity advantage over a positional list.

The real infrastructure associated with frontend remains associated with:

some_provider_server.app["frontend"]

rather than becoming “whatever is now at index 0.”

depends_on as a Meta-Argument

depends_on is also a Terraform meta-argument:

resource "some_provider_server" "web" {
  name = "web"

  depends_on = [
    some_provider_security.policy
  ]
}

It adds an explicit dependency to the graph.

As discussed in the previous chapter, prefer natural resource references when possible:

network = some_provider_network.main.id

Use depends_on when the dependency is real but Terraform cannot infer it from an expression.

Do not use it simply to make Terraform execute resources in the order you happen to prefer.

Meta-Arguments and State Identity

count and for_each are especially important because they change resource addresses.

Compare:

some_provider_server.web

with:

some_provider_server.web[0]

and:

some_provider_server.web["frontend"]

These are different resource instance addresses.

Terraform uses those addresses when tracking instances in state.

This is why changing from one addressing scheme to another is not merely a cosmetic configuration change. Terraform may otherwise interpret it as removing one resource and creating another.

That becomes especially important when you later learn about state-aware refactoring in Chapter 14.

Warning

Before changing count/for_each structure on an existing resource, inspect the resulting plan carefully. You are changing how Terraform identifies resource instances.

Choosing Between Them

Use count when you genuinely want positional instances:

worker-0
worker-1
worker-2

Use for_each when the instances have stable identities:

frontend
backend
worker

If you find yourself worrying about what happens when an item is inserted or removed from a list, that is often a sign that keys would describe your infrastructure better than indexes.

The next chapter looks at a different class of Terraform behavior: what happens when a resource argument changes and Terraform cannot update the existing object in place.

Last updated on