Study guide
6.3 Resource Addressing and Interpolation Patterns
HashiCorp Certified: Terraform Associate 004
Track your progress as you read
Finished sections get a check mark.
1 Introduction & Exam Overview
1.1 Current Exam Facts 1.2 Format, Delivery, Retakes, and Recertification 1.3 004 Changes, Terraform 1.12, and Study Plan
2 Infrastructure as Code with Terraform
2.1 What Infrastructure as Code Is 2.2 Advantages of IaC Patterns 2.3 Multi-Cloud, Hybrid, and Service-Agnostic Workflows
3 Terraform Fundamentals: Providers & State
3.1 Install and Version Terraform Providers 3.2 How Terraform Uses Providers 3.3 Configuration with Multiple Providers 3.4 How Terraform Uses and Manages State
4 Core Terraform Workflow: Init & Validate
4.1 The Terraform Write–Plan–Apply Workflow 4.2 Initialize a Working Directory 4.3 Validate a Configuration
5 Plan, Apply, Destroy, and Formatting
5.1 Generate and Review an Execution Plan 5.2 Apply Changes to Infrastructure 5.3 Destroy Terraform-Managed Infrastructure 5.4 Formatting and Style Adjustments
6 Resources, Data Sources, and References
6.1 Resource Blocks vs Data Blocks 6.2 Resource Attributes and Cross-Resource References 6.3 Resource Addressing and Interpolation Patterns
7 Variables, Outputs, and Complex Types
7.1 Input Variables, tfvars, and Precedence 7.2 Output Values 7.3 Complex Types: list, map, object, tuple, set
8 Dynamic Configuration and Dependencies
8.1 Expressions, count, for_each, and dynamic Blocks 8.2 Built-in Functions 8.3 Dependencies, depends_on, and Lifecycle Rules
9 Conditions, Secrets, and Sensitive Data
9.1 Custom Conditions: validation, precondition, postcondition, check 9.2 Sensitive Data Handling 9.3 Vault, Ephemeral Values, and Write-Only Arguments
10 Terraform Modules
10.1 How Terraform Sources Modules 10.2 Variable Scope within Modules 10.3 Using Modules in Configuration 10.4 Managing Module Versions
11 Terraform State Management
11.1 The Local Backend 11.2 State Locking 11.3 Remote State and the backend Block 11.4 Resource Drift and State Operations
12 Maintain Infrastructure with Terraform
12.1 Import Existing Infrastructure 12.2 Inspect State with the CLI 12.3 Verbose Logging and Diagnostics
13 HCP Terraform
13.1 Create Infrastructure with HCP Terraform 13.2 Collaboration and Governance Features 13.3 Workspaces and Projects 13.4 HCP Terraform Integrations
6.3 Resource Addressing and Interpolation Patterns
Key Takeaways Explain This Topic AI
A resource address identifies instances in configuration and state: aws_instance.web, aws_instance.web[0], aws_instance.web["app"], module.vpc.aws_subnet.private[1], data.aws_ami.al2023
count uses a 0-based numeric index; for_each uses a quoted key — do not mix aws_instance.web[0] with a for_each resource
Inside provisioner and connection blocks, self is the parent resource; referring to the resource by name would create a cycle. Provisioners are a last resort and not a 004 focus
terraform apply -replace=aws_instance.web forces replacement of that address; a moved block records a rename so Terraform does not destroy
Do not confuse var.name with a resource address, and do not treat module.vpc.aws_subnet.private[1] as a parent-module expression — that shape is a CLI address, while configuration uses module outputs
Last updated: August 2026
6.3 Resource Addressing and Interpolation Patterns
Quick Answer: An address is [module path][resource spec] . Root examples: aws_instance.web , aws_instance.web[0] , aws_instance.web["app"] , data.aws_ami.al2023 . Child-module CLI form: module.vpc.aws_subnet.private[1] . count uses a numeric index; for_each uses a key. self exists only in provisioner and connection blocks. -replace=aws_instance.web forces replacement. A moved block renames without destroying.
This section stays on objective 4b — refer to resource attributes and create cross-resource references — and adds the address vocabulary HashiCorp lists next to that objective. You use the same strings in terraform state show , -target , and -replace . Terraform 1.12 still documents this syntax on Resource Addressing .
- The address grammar
- Official syntax is two optional parts: [module path][resource spec]
Omit the module path and you are in the root module. A resource spec is resource_type.resource_name[instance index] . Data sources use the same spec with a data. prefix.
Address What it selects
aws_instance.web The whole managed resource in the root module. If count or for_each is set, this is all instances
- aws_instance.web[0] The first instance of a count resource (indexes are 0-based )
- aws_instance.web["app"] The for_each instance whose key is app
- data.aws_ami.al2023 The root data source named al2023
- module.vpc.aws_subnet.private[1] Inside child module vpc , the count instance [1] of aws_subnet.private
- module.foo[0].module.bar["a"].aws_instance.web Nested modules, each with its own index or key
Index versus key is a pure 004 trap:
count = 4 → instances [0] , [1] , [2] , [3] . aws_instance.web[0] is legal. aws_instance.web["0"] is a different (usually invalid) key.
for_each = toset(["app", "job"]) or a map → instances ["app"] , ["job"] . aws_instance.web["app"] is legal. aws_instance.web[0] is not.
Omitting the index on a multi-instance resource means all instances in CLI contexts such as -target . In an HCL expression, aws_instance.web is then a list ( count ) or a map ( for_each ), so you still need [0] , ["app"] , a splat, or values(...) to reach .id .
CLI address versus configuration expression
This distinction is easy to miss and the exam likes it.
From a parent module's HCL , module.vpc is an object of outputs . You write module.vpc.private_subnet_ids[1] . You cannot reach through the child and write module.vpc.aws_subnet.private[1].id unless the child literally exported an output with that structure — and it should not.
From the CLI , module.vpc.aws_subnet.private[1] is the resource address in state. That is what you pass to -replace , -target , terraform state show , and terraform state mv .
Inside the child module , the resource is just aws_subnet.private[1] . The module.vpc. prefix appears only when you stand outside that module.
- Context Legal Illegal or wrong idea
- Parent HCL module.vpc.private_subnet_ids[1] module.vpc.aws_subnet.private[1].id as if internals were public
- CLI / state module.vpc.aws_subnet.private[1] var.vpc.aws_subnet.private[1]
- Inside module vpc aws_subnet.private[1].id module.vpc.aws_subnet.private[1] (you are already inside)
- Input variable var.ami_id var.aws_instance.web
var. is never a resource address. var.web is an input variable named web . If the question shows var.aws_instance.web.id , it is wrong.
Interpolation patterns next to addresses
Section 6.2 covered first-class expressions. Addresses show up inside interpolations when you are building a string, not when you are selecting an instance: resource "aws_instance" "web" {
- count = 2
- ami = data.aws_ami.al2023.id
- tags = {
- Name = "web-${count.index}" # interpolation inside a string
- }
- }
- output "first_id" {
- value = aws_instance.web[0].id # expression, no ${}
- }
- output "banner" {
- value = "first instance is ${aws_instance.web[0].id}"
- }
count.index and each.key / each.value are block-local names. They are not resource addresses. "web-${count.index}" is a string template. aws_instance.web[count.index] (from another resource that also has count ) is an indexed reference, still a first-class expression.
Escape a literal ${ in a string with $${ . That is a template rule, not an addressing rule.
self , provisioners, and why 004 barely cares
Expressions in provisioner and connection blocks cannot refer to their parent resource by name. That reference would be a dependency cycle: the resource would depend on itself. HashiCorp's provisioner docs say to use the special self object, which is the parent resource. self.public_ip , self.private_ip , and self.id are the usual examples. resource "aws_instance" "web" {
- ami = data.aws_ami.al2023.id
- instance_type = "t3.micro"
- provisioner "local-exec" {
- command = "echo ${self.private_ip}"
- }
- }
HashiCorp also warns, repeatedly, that provisioners are a last resort . Terraform cannot model them in the plan the way it models create and destroy, and most of them need network access plus credentials onto the box. 004 is not a provisioner exam. Remember self for provisioner and connection blocks (custom precondition / postcondition checks also use self — that is objective 4g). Prefer cloud-init, machine images, or a real configuration-management tool over provisioners.
-replace uses an address
terraform apply -replace=aws_instance.web (or terraform plan -replace=... ) instructs Terraform to replace that instance even if configuration would otherwise update it in place or do nothing. Official plan documentation: this is for a degraded remote object you want to recreate with the same configuration. Repeat the flag to replace several addresses. You cannot combine -replace with -destroy . On Terraform 1.12 this is the current workflow; terraform taint is the older path.
If the resource uses count or for_each , pass a specific instance when you mean one object: -replace=aws_instance.web[0] or -replace='aws_instance.web["app"]' . Quotes around a key often need shell quoting.
-target also takes a resource address, but HashiCorp documents it as an exceptional recovery tool, not routine workflow. Do not treat -target as the way you "apply one resource" day to day.
moved on Terraform 1.12, at exam depth
A moved block is current on 1.12 (it shipped in 1.1). 004 lists it under state-management objective 6d ; you still need a light 4b reading because the block is written in address syntax: resource "aws_instance" "web" {
- # formerly labeled "old"
- }
- moved {
- from = aws_instance.old
- to = aws_instance.web
- }
Before planning aws_instance.web , Terraform looks in state for aws_instance.old and renames that object. The next plan does not destroy the instance. You can move a whole resource, a single instance ( from = aws_instance.web[0] → to = aws_instance.web["app"] ), or a module call ( from = module.a → to = module.b ). You cannot use moved to turn a managed resource into a data source.
moved is not -replace . Replace recreates the remote object. moved only changes the address in state.
004 traps for addresses and interpolation
aws_instance.web[0] is count . aws_instance.web["app"] is for_each . Mixing them is a graph error.
module.vpc.aws_subnet.private[1] is a CLI / state address. Parent HCL uses module outputs .
var. never selects a resource.
data.aws_ami.al2023 is the data-source address; dropping data. looks for a managed resource.
self is not valid in ordinary resource arguments. It is the parent object in provisioner, connection, and custom-condition blocks.
-replace is not moved , and it is not destroy.