linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/kb/Terraform basics/tf-references

kb/core ── Terraform basics ── beginner

References in HCL: how to read aws_s3_bucket.demo.bucket

Any value in HCL is reachable through an address: var.x, local.x, aws_s3_bucket.demo.arn, module.net.vpc_id, data.aws_region.current.name. Knowing this syntax is half of your productivity in Terraform.

view as markdown

Why references matter

In HCL almost everything is a reference. Does the bucket name depend on the region? Reference data.aws_region. Does the name depend on a variable? var.something. Do you need an attribute of one resource inside another? aws_s3_bucket.demo.arn.

Without a solid grasp of the reference syntax, Terraform turns into copy and paste with no understanding. With it, the opposite is true.

The five main prefixes

PrefixWhatExample
var.nameVariablevar.region
local.nameLocallocal.name_prefix
type.name.attributeResourceaws_s3_bucket.demo.arn
data.type.name.attributeData sourcedata.aws_region.current.name
module.name.output_nameOutput of a child modulemodule.network.vpc_id

There are also special ones available inside blocks:

  • count.index, inside a block with count.
  • each.key, each.value, inside a block with for_each.
  • self.attribute, inside a provisioner (advanced).

Simple examples

hcl
variable "env" { default = "dev" }
locals {
  prefix = "myapp-${var.env}"
}
data "aws_region" "current" {}
resource "aws_s3_bucket" "logs" {
  bucket = "${local.prefix}-logs-${data.aws_region.current.name}"
  # myapp-dev-logs-us-east-1
}
resource "aws_s3_bucket_versioning" "logs" {
  bucket = aws_s3_bucket.logs.id   # reference to the first bucket
  versioning_configuration {
    status = "Enabled"
  }
}

Inside ${...} vs without it

In modern HCL you need ${...} only when the expression is part of a string:

hcl
# part of a string: ${...} is required
bucket = "myapp-${var.env}-${local.region}"
# whole expression: quotes and ${...} are not needed
bucket = local.bucket_name
count  = var.instance_count
region = data.aws_region.current.name

The old style "${var.foo}" for a single expression still works, but Terraform will complain with the warning "Interpolation-only expressions are deprecated".

The splat operator [*]: take one field from all of them

If a resource is created with count or for_each, it becomes a list or a map. To get, for example, every arn:

hcl
resource "aws_s3_bucket" "logs" {
  count  = 3
  bucket = "logs-${count.index}"
}
# all ARNs as a list
output "all_arns" {
  value = aws_s3_bucket.logs[*].arn
  # = [aws_s3_bucket.logs[0].arn, aws_s3_bucket.logs[1].arn, aws_s3_bucket.logs[2].arn]
}

The [*] sign is called the splat operator. It applies to count and for_each in the same way.

An alternative syntax uses for:

hcl
output "all_arns" {
  value = [for b in aws_s3_bucket.logs : b.arn]
}

Addresses with an index

hcl
# count
aws_s3_bucket.logs[0]                # first
aws_s3_bucket.logs[length(aws_s3_bucket.logs) - 1]  # last
# for_each
aws_s3_bucket.regional["us"]
aws_s3_bucket.regional["eu"].arn
# inside a resource with count
resource "aws_s3_bucket" "logs" {
  count  = 3
  bucket = "logs-${count.index}"   # 0, 1, 2
}
# inside a resource with for_each
resource "aws_s3_bucket" "regional" {
  for_each = toset(["us", "eu"])
  bucket   = "data-${each.key}"     # us, eu
  tags     = { region = each.value }  # same value as each.key for a set
}

Which attributes a resource has

When you write aws_s3_bucket.demo.X, which values of X are available? That depends on the provider. The list is in the provider documentation:

  • https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket

The "Attributes Reference" section is what you can read. The "Arguments" section is what you can set.

Tip: remember the address pattern terraform.io/providers/<vendor>/<provider>/latest/docs/resources/<resource_name>, you will use it constantly.

Pitfalls

  • local vs locals again. The declaration is locals { ... }, the reference is local.name. No s.

  • A resource with count is a list in state, and an address without an index does not work everywhere. aws_s3_bucket.logs.arn is an error when count > 1. You need aws_s3_bucket.logs[0].arn or aws_s3_bucket.logs[*].arn.

  • var.name is for a variable, not for locals. Beginners often write var.prefix while meaning a local. These are two different namespaces.

  • (known after apply) breaks your intuition. This happens when you reference an attribute that is computed on the cloud side. Terraform knows that the value will appear, but does not show it in the plan. That is normal.

  • Circular dependencies are fatal. This happens when A references B and B references A. Terraform fails with "cyclic dependency". The fix is tf-locals or a rethink of the architecture.

  • You cannot reference a resource from inside its own block. resource "x" { foo = self.bar } does not work: self is available only in provisioner blocks. Normally there is no way around it.

§ команды

bash
terraform console

Check the value of a reference interactively. It helps a lot with splat and for expressions.

bash
terraform state list

The full list of addresses in the current state: copy them straight from the output.

bash
terraform state show aws_s3_bucket.demo

All attributes of a resource: see what you can reference.

§ см. также

  • tf-resource-blockResource block: the main building block of TerraformA resource block tells Terraform "create this thing in the cloud." It has three parts: the resource type (what it is), the name (how you refer to it internally), and the arguments (how to configure it). Writing these blocks is what you spend 90% of your time doing in Terraform.
  • tf-variableThe variable block: input to your configurationA variable is a parameter that receives its value from outside the configuration (CLI, environment variable, .tfvars file). You declare it in HCL with type, default, description, and validation, then reference it as var.name. Variables remove hardcoded values and let one HCL configuration serve multiple environments.
  • tf-localslocals: computed internal nameslocals is a block with names visible only inside HCL (not input, not output). Useful for DRY: compute a common prefix or tag set once, then use it everywhere via local.x. Do not confuse with variable (input) and output (output).
  • tf-outputThe output block: what Terraform exposes to the outsideAn output is a value that Terraform displays after apply and stores in state. Use it to (a) show the user the ID or ARN of a created resource, (b) pass values between modules, or (c) feed values to scripts via `terraform output -raw`.
  • tf-count-for-eachcount and for_each: many resources from one blockcount creates N identical resources by index 0..N-1. for_each creates resources keyed from a set or map. Rule of thumb: count for identical copies, for_each when each one has its own settings. When in doubt, use for_each.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies