Domain 4 of 8 · Chapter 2 of 8

Referencing resource attributes

Referencing resource attributes

A Terraform argument rarely stands alone: most of the time it borrows a value another object already produced, and the mechanism is a reference, a dotted address whose first token says what kind of thing you are naming. The most common target is another resource's exported attribute, written <TYPE>.<NAME>.<ATTRIBUTE>. If an aws_vpc resource named main exports an id, then aws_vpc.main.id[1] reads that id wherever you need it. That address takes two variants once a resource is repeated, and those variants are the rest of this section.

A counted resource is a list

Adding count[2] to a resource turns it into a list of objects, one per instance. You select an instance by numeric index, aws_instance.web[0], and read an attribute from it, aws_instance.web[0].id. The bare address aws_instance.web now means the whole list, so aws_instance.web.id with count set is an error: you asked a list for an attribute only a single object has. Inside the block, count.index[2] is this instance's distinct index number starting at 0, which is how you vary an argument across the copies.

A for_each resource is a map

for_each[3] instead accepts a map or a set of strings and turns the resource into a map of objects keyed by those keys. You select an instance by its string key, aws_instance.web["app"], and read aws_instance.web["app"].id. A numeric index like [0] fails here, because the collection is keyed by string, not by position. Inside the block, each.key[3] is the current key and each.value is its value (for a set, the two are identical).

One idea sits under all three cases: a plain resource is a single object, count makes a list you index by number, and for_each makes a map you index by key. The selector follows directly from which collection you created.

aws_instance.webone resource blockno count/for_eachcountfor_eachSingle objectaws_instance.web.idread the attribute directlyList of objectsaws_instance.web[0].idcount.index inside the blockMap of objectsaws_instance.web["app"].ideach.key / each.value inside
Addressing a resource: a plain resource is one object, count makes a list indexed by number, for_each makes a map indexed by string key.

References set order and known-after-apply

A reference does more than copy a value: it also fixes the order Terraform works in.

When one resource's argument references another resource's attribute, for example subnet_id = aws_subnet.main.id on an instance, Terraform reads that as a dependency and must create aws_subnet.main before the instance that uses its id. Terraform infers this by studying the attributes used in interpolation expressions[4] across the whole configuration, builds a dependency graph, and walks that graph to decide the create and update order. You write no ordering directive; the reference is the directive.

depends_on is only for hidden links

Because references already imply order, depends_on[5] is reserved for the case where a resource relies on another's behavior but reads none of its data, so no expression reveals the link. Adding depends_on beside a reference that already connects the two is redundant, not safer. HashiCorp recommends expression references over depends_on[5] wherever a value can carry the dependency, because a reference also tells Terraform which value the order depends on.

Values known only after apply

Some attributes are assigned by the provider when it creates the object, an EC2 instance id or an ARN, so their value cannot exist at plan time. Terraform still plans successfully: it prints the not-yet-known value as (known after apply)[6] and fills in the real value during apply. A reference to such an attribute propagates that unknown, so a dependent argument also shows (known after apply) in the plan. That is expected output, not a failure; the value resolves the moment the upstream resource is created.

Write subnet_id = aws_subnet.main.ida reference to another resourceTerraform infers an implicit dependencyno depends_on neededDependency graph orders the worksubnet before the instancePlan shows (known after apply)id not yet assignedApply builds the subnet, fills the iddependent value resolves
A reference sets order: Terraform builds the referenced subnet first and shows any provider-assigned id as (known after apply) until apply fills it.

Named value reference forms

Resource attributes are one family of references; every other value Terraform exposes has its own fixed prefix, and knowing the prefix is knowing the reference.

The named values you reach by prefix:

Reference form Names Example
var.<NAME> an input variable var.region
local.<NAME> a local value local.common_tags
module.<NAME>.<OUTPUT> a child module's declared output module.network.vpc_id
data.<TYPE>.<NAME> a data source's result data.aws_ami.ubuntu.id
path.module, path.root, path.cwd filesystem paths path.module
terraform.workspace the current workspace name terraform.workspace

All of these are documented on the single references[1] page, and path.* and terraform.workspace are usable anywhere in a module.

The locals block: plural keyword, singular reference

A locals block[7] assigns names to expressions so a value computed once can be reused. The keyword is plural, locals, but you reference a single value with the singular local.<NAME>: locals { name = ... } defines it and local.name reads it. Writing locals.name is a frequent slip. A local is internal to the module that defines it; you can access it in that module but not in others[7], and a caller cannot set it. That is the line between a local and an input variable: a variable takes external input, a local only names an internal expression.

Values that exist only inside a block

A few names are valid only in a specific context. count.index, each.key, and each.value live only inside a block that sets count or for_each (the addressing rule from the first section), and self is valid only inside provisioner and connection blocks, where it refers to the resource being provisioned. Outside their block these names resolve to nothing and Terraform rejects the configuration.

How this appears on the exam

This objective is tested by spotting the reference that looks right but is not. Each trap below restates a rule established above.

  • Unindexed reference to a counted resource. With count set, aws_instance.web is a list, so aws_instance.web.id is an error; the correct form indexes first, aws_instance.web[0].id.
  • Numeric index on a for_each instance. A for_each resource is keyed by string, so aws_instance.web[0] fails; use the key, aws_instance.web["app"].
  • Bare variable or local name. region is not a reference to a variable; var.region is. Likewise a local is local.name, never the bare name and never the plural locals.name.
  • A redundant depends_on. If an argument already references the other resource's attribute, the dependency already exists; adding depends_on for the same pair is unnecessary. depends_on is the answer only when there is no shared value to reference.
  • Reaching a module's internals. A parent can read module.network.vpc_id (a declared output) but not the child's internal resources; "reference the module's aws_vpc directly" is always wrong.
  • (known after apply) treated as an error. A computed id shown as (known after apply) in the plan is normal; the value is assigned during apply. An answer that calls this a failure, or that requires a depends_on to fix it, is wrong.
  • A block-local value out of context. count.index outside a counted block, or self outside a provisioner or connection block, is invalid; a stem that uses one of these in a plain resource argument is the distractor.

Addressing a resource and its instances

AspectSingle resourceWith countWith for_each
Instance collectionOne objectList of objectsMap of objects
Reference one instance`aws_instance.web``aws_instance.web[0]``aws_instance.web["app"]`
Bare address givesThe single objectThe whole listThe whole map
Block-local iteratorNone`count.index``each.key` / `each.value`

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Attribute reference form

Another resource's exported attribute is referenced as <TYPE>.<NAME>.<ATTRIBUTE>, for example aws_vpc.main.id.

6 questions test this
Indexing count instances

For a resource using count, individual instances are addressed by numeric index such as aws_instance.web[0].id; the bare name aws_instance.web is the whole list of instances.

Trap Referencing aws_instance.web.id directly when count is set, which errors because the value is a list.

5 questions test this
Indexing for_each instances

For a resource using for_each, instances are addressed by their map/set key, such as aws_instance.web["app"].id; the collection is a map keyed by each.key.

Trap Using a numeric index like [0] to address a for_each instance instead of its string key.

4 questions test this
References create implicit dependencies

When one resource argument references another resource's attribute (e.g. subnet_id = aws_subnet.main.id), Terraform automatically infers a dependency and creates or updates the referenced resource first.

Trap Adding a manual depends_on when a reference already establishes the ordering.

12 questions test this
Unknown referenced values

If a referenced attribute is not yet known at plan time (for example an ID the provider assigns on create), the plan shows the dependent value as (known after apply).

8 questions test this
Named value prefixes

Terraform references named values by prefix: input variables as var.<NAME>, local values as local.<NAME>, data sources as data.<TYPE>.<NAME>, and child-module outputs as module.<NAME>.<OUTPUT>.

Trap Referencing a variable by its bare name instead of var.<name>, or reaching a child module's internal resource instead of its declared output.

11 questions test this
Path and workspace values

path.module, path.root, and path.cwd give filesystem paths and terraform.workspace gives the current workspace name; self, count.index, and each.key/each.value are only valid inside specific block contexts.

locals block for named local values

A locals block assigns names to expressions so a value can be computed once and reused, referenced elsewhere as local. (singular local, even though the block keyword is locals). Local values keep configuration DRY and readable, but unlike input variables they are internal to the module and cannot be set or overridden by callers.

Trap Reference a local with the singular local., not the plural locals.; and because callers cannot set locals, a local is not a substitute for a variable when you need external input.

7 questions test this

References

  1. References to Named Values
  2. The count Meta-Argument
  3. The for_each Meta-Argument
  4. Create Resource Dependencies (tutorial)
  5. The depends_on Meta-Argument
  6. Create a Terraform Plan (tutorial)
  7. Local Values