Skip to main content
RunBook Academy

TerraformVII · Resources, Data Sources, and count/for_eachProduction Terraform

Resource Attributes and Computed Values

Foundation⏱ ~12 minbash

What you'll learn

  • Distinguish required arguments from optional arguments in a resource block
  • Identify computed, optional-computed, and required-computed attribute kinds
  • Recognise "known after apply" and explain why it affects planning
  • Use attribute references to compose resources without circular dependencies

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

Every resource block is a set of named fields. Some of them you write; some of them the provider writes back to you. Some you must supply; some you may leave blank. The vocabulary around these fields is inconsistent in the wild — the Terraform documentation uses “argument” and “attribute” interchangeably — and the consequences of confusing them show up at the worst moment: during a production apply. This lesson is the disciplined reading of the resource schema.

Argument versus attribute

The Terraform documentation draws a fine distinction that the wider community ignores:

  • Argument — a field the user writes in the configuration. Appears on the left-hand side of = inside the resource block. Used as input to the provider API.
  • Attribute — any named field exposed by a resource. Includes all arguments (since they are also readable after apply) plus the provider-computed fields.

In practice, the distinction matters when you try to write to a field that the provider only writes. The error message tells you:

Error: Invalid value for attribute

  on main.tf line 12, in resource "aws_instance" "web":
  12:   arn = "arn:aws:ec2:eu-west-1:..."

The given attribute is read-only.

arn is an attribute, not an argument. You cannot set it; the provider populates it.

The schema kinds

A provider declares each attribute with one or two boolean flags in its schema:

KindUser can set?Provider writes?Default?
RequiredYes (must)NoNone — must be supplied
OptionalYes (may)NoPer schema default
ComputedNoYesProvider default
Optional + ComputedYes (may)YesProvider default if not set
Required + ComputedYes (must, but only after creation)YesProvider default on creation

A real example using the AWS provider:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"  # Required argument
  instance_type = "t3.small"                 # Required argument
  monitoring    = false                      # Optional argument, default false
  subnet_id     = aws_subnet.public["a"].id  # Required argument, cross-resource reference
  tags = {                                   # Optional argument
    Name = "web-1"
  }

  # The following are Computed attributes. You can read them; you cannot write them.
  # arn, id, private_dns, public_ip, password_data (when applicable).
}

The error above (is read-only) is the hallmark of writing to a Computed-only attribute. The fix is to remove the assignment and either reference the attribute elsewhere or use a different resource attribute that the provider does accept as input.

Known after apply

Some attributes are Computed and the provider cannot determine their value until the resource is created. The plan reports these as (known after apply). Example:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + ami                  = "ami-0c55b159cbfafe1f0"
      + arn                  = (known after apply)
      + id                   = (known after apply)
      + instance_type        = "t3.small"
      + private_dns          = (known after apply)
      + public_ip            = (known after apply)
      # ...
    }

A (known after apply) value cannot be referenced in the same apply. It is resolved at the end of the apply phase, after the provider returns the live values. This is why a configuration that references (known after apply) of a resource it has just declared fails with a self-reference error.

The rule is: an attribute can only be referenced from a resource created earlier in the same apply, or in a separate apply.

Inspecting the schema

The provider ships its schema with the plugin. Inspect it on the command line:

terraform providers schema -json | jq '.provider_schemas."registry.terraform.io/hashicorp/aws".resource_schemas."aws_instance".block.attributes | to_entries[] | {name: .key, type: .value.type, required: .value.required, optional: .value.optional, computed: .value.computed}'
{
  "name": "arn",
  "type": "string",
  "required": false,
  "optional": false,
  "computed": true
}
{
  "name": "instance_type",
  "type": "string",
  "required": true,
  "optional": false,
  "computed": false
}
{
  "name": "monitoring",
  "type": "bool",
  "required": false,
  "optional": true,
  "computed": false
}

The schema is the source of truth for what is required, what is optional, and what is read-only. Read it before writing the configuration, not after.

Reading attributes after apply

The point of having computed attributes is that they let you reference values you did not write — and that you could not have known without creating the resource. A typical pattern:

resource "aws_lb" "web" {
  name               = "web"
  load_balancer_type = "application"
  subnets            = [for s in aws_subnet.public : s.id]
}

output "lb_dns_name" {
  value = aws_lb.web.dns_name
}

aws_lb.web.dns_name is a Computed attribute. The provider assigns it during apply. The output block surfaces it for downstream consumers. The same pattern is used for IDs, ARNs, generated passwords, and any other value the provider owns.

Production failure modes

  1. Writing to a computed-only attribute. The apply fails with Invalid value for attribute or is read-only. Symptom: the plan succeeds (validation only checks syntax) but the apply fails at the first resource that violates the schema. Recovery: remove the assignment, use the attribute as an output or reference it from a different resource.

  2. Relying on an Optional default that changed. A new provider version changes the default of an optional argument. The plan shows ~ for a resource that the team did not modify. Symptom: the diff shows monitoring = true -> false for many resources on the next apply after a provider upgrade. Recovery: pin the provider version in the required_providers block, or set the attribute explicitly.

  3. Self-reference to a known after apply attribute. A resource block references its own computed attribute. Symptom: the apply fails with “self-dependency” or “cycle”. Recovery: split the apply into two runs, or move the dependent value into a data source lookup rather than an output of the same block.

  4. Treating an Optional + Computed attribute as user-set forever. If you set the attribute to null, the provider keeps it null for the lifetime of the resource (it is in state). Symptom: the attribute does not get the value the provider would have chosen. Recovery: terraform state rm the attribute and let the next refresh repopulate it, or set it to a real value.

  5. Sensitive value in logs. A computed sensitive attribute (e.g. password) is written into a tag, an output without sensitive, or a local_file resource. Symptom: the value shows up in CI logs or a backup bucket. Recovery: redact logs, rotate the credential, mark the output sensitive = true.

  6. Assuming the schema is stable across provider versions. Provider upgrades may rename attributes, change defaults, or move attributes into nested blocks. Symptom: a plan after a provider upgrade shows many ~ lines for attributes the team did not change. Recovery: read the provider upgrade guide; pin the provider version explicitly.

Security and performance

Security. Computed attributes can be sensitive. Treat the state file as a sensitive artefact regardless of which attributes are marked Sensitive. Encrypt the state at rest, restrict access to the state backend, and never commit state to Git.

Performance. Reading an attribute is free. Computing it may not be — the provider may call an extra API to read the live value back. For attributes that are returned by the create API, the cost is zero. For attributes that require a follow-up read, the cost is the latency of the read API. Check the provider documentation if the apply is performance-sensitive.

What to do in production

  • Pin provider versions in required_providers. Schema drift between provider versions is the most common cause of unexpected attribute changes.
  • Run terraform plan -out=tfplan for every change and read the schema diffs.
  • Use terraform providers schema -json to inspect what is required and what is computed for any resource type you are about to use.
  • For sensitive computed attributes, mark the consuming output or variable sensitive = true. The value is redacted from plan output but is still in state.
  • Avoid reading computed attributes from the same apply in which the resource is created; if you must, split the apply.

Verification

# 1. Inspect the schema for a specific resource type
terraform providers schema -json | \
  jq '.provider_schemas."registry.terraform.io/hashicorp/aws".resource_schemas."aws_instance".block.attributes'

# 2. Confirm no computed-only attribute is being assigned
grep -E '^\s*(arn|id|dns_name|private_dns)\s*=' main.tf

# 3. Show the known-after-apply attributes in the current plan
terraform plan -out=tfplan
terraform show -json tfplan | jq '.resource_changes[] | select(.change.after_unknown != {}) | .address'

# 4. Confirm sensitive attributes are redacted in the plan
terraform show -json tfplan | jq '.resource_changes[] | select(.change.after_sensitive != {}) | .address'

# 5. Verify state contains the expected computed attributes
terraform state show aws_instance.web

A clean verification looks like:

$ terraform plan -out=tfplan
$ terraform show -json tfplan | jq '.resource_changes[] | select(.change.after_unknown != {}) | .address'

The second command should print only the address of any resource whose plan contains known after apply values. For a configuration that references computed attributes from another apply, this list should be non-empty and explainable.

Knowledge check · 7 questions

  1. Q1. Which of the following best describes the difference between an argument and an attribute?

  2. Q2. An attribute is declared `Optional + Computed` in the provider schema. Which is true?

  3. Q3. A `Computed`-only attribute is read-only: Terraform errors if the configuration tries to set it.

  4. Q4. Which of the following are valid attribute kinds in a Terraform provider schema? (Select all that apply.)

  5. Q5. Why does `terraform plan` show `(known after apply)` for an attribute?

  6. Q6. What command inspects the provider schema on the command line?

  7. Q7. A team upgrades the AWS provider from 5.0 to 5.20. Their next plan shows dozens of `~` lines for attributes they did not change. What is the most likely cause?

Passing score: 75%. Answers are checked in this browser.