Arguments & Attributes
Every resource block has two sides: the values you write in and the values Terraform reads back out. Arguments are the inputs you configure to describe the desired state; attributes are the facts about the real object after it exists. The distinction matters because you cannot set an attribute, and you usually cannot know it until after terraform apply has talked to the provider’s API. Getting this model right is what makes references like aws_instance.web.id work, and it applies identically to OpenTofu.
Arguments: what you configure
Arguments are the name = value settings inside a resource block. They express the desired state — what you want the object to look like. The provider validates them, sends them to the upstream API on create or update, and stores them in state.
resource "aws_instance" "web" {
ami = "ami-0c2b8ca1dc6cf8160"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}
Here ami, instance_type, and tags are arguments. Each provider schema marks an argument as required, optional, or optional-with-default. Required arguments must appear or the plan fails; optional ones fall back to a default or to the API’s own default when omitted. Changing an argument is what drives an update (or a replacement) on the next apply.
Attributes: what Terraform computes
Attributes are values that exist only after the object is created. The provider returns them from the API and Terraform records them in state. You read attributes; you never assign them. The canonical examples are identifiers the cloud generates for you — id, arn, private_ip, public_dns.
output "instance_id" {
value = aws_instance.web.id
}
output "instance_arn" {
value = aws_instance.web.arn
}
Because these are computed, they show up in a plan as (known after apply) — Terraform genuinely cannot know them until the resource is built.
Output:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0c2b8ca1dc6cf8160"
+ instance_type = "t3.micro"
+ arn = (known after apply)
+ id = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
The + lines that carry literal values are arguments you set; the (known after apply) lines are computed attributes.
Arguments vs attributes at a glance
| Aspect | Argument | Attribute |
|---|---|---|
| Direction | Input — you set it | Output — Terraform reads it |
| Known when | Before apply (config time) | After apply (from the API) |
| In a plan | Shows the literal value | Shows (known after apply) |
| Can you assign it? | Yes | No |
| Drives changes? | Yes — edits trigger update/replace | No — it reflects reality |
| Example | instance_type, tags | id, arn, private_ip |
Some schema fields are both: an optional + computed attribute can be set by you or, if you omit it, filled in by the provider. A security group’s name is a common example — supply one, or let AWS generate name_prefix-style values and read them back.
Reading attributes after apply
The most useful thing about attributes is wiring one resource’s computed output into another resource’s argument. This is how Terraform builds its dependency graph automatically — referencing aws_instance.web.id tells Terraform the instance must exist first.
resource "aws_instance" "web" {
ami = "ami-0c2b8ca1dc6cf8160"
instance_type = "t3.micro"
}
resource "aws_eip" "web" {
instance = aws_instance.web.id # attribute -> argument
domain = "vpc"
}
After apply, inspect attributes from the CLI without editing config:
terraform output instance_id
terraform state show aws_instance.web
terraform console
Referencing an attribute creates an implicit dependency. Prefer this over a manual
depends_on— let the reference express ordering so Terraform parallelizes everything else.
Sensitive and computed attributes
Some attributes hold secrets — generated passwords, private keys, tokens. Providers mark these sensitive, so Terraform redacts them in plan and apply output. They still live in plaintext inside state, which is why state must be stored securely.
resource "aws_db_instance" "main" {
identifier = "app-db"
engine = "postgres"
instance_class = "db.t3.micro"
allocated_storage = 20
username = "appuser"
manage_master_user_password = true
}
output "db_endpoint" {
value = aws_db_instance.main.endpoint
}
Output:
aws_db_instance.main: Creating...
aws_db_instance.main: Creation complete after 4m12s [id=app-db]
Outputs:
db_endpoint = "app-db.abc123.us-east-1.rds.amazonaws.com:5432"
If you expose a sensitive attribute through an output, Terraform forces you to mark the output sensitive = true or it errors. Marking it hides the value from CLI display but does not encrypt it in state.
output "db_password" {
value = aws_db_instance.main.password
sensitive = true
}
Best Practices
- Read provider documentation for each resource — it lists exactly which fields are arguments, which are attributes, and which are both.
- Wire resources together by referencing attributes rather than hardcoding IDs; this builds correct ordering automatically.
- Expect
(known after apply)for computed values and never try to assign them — assigning an attribute is a configuration error. - Treat state as secret: sensitive attributes are stored in plaintext, so use an encrypted remote backend with locking.
- Mark any output that surfaces a credential as
sensitive = true, and avoid logging sensitive attributes in CI. - Use
terraform consoleandterraform state showto explore available attributes before writing a brittle reference.