Skip to content
Infrastructure as Code iac resources 4 min read

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

AspectArgumentAttribute
DirectionInput — you set itOutput — Terraform reads it
Known whenBefore apply (config time)After apply (from the API)
In a planShows the literal valueShows (known after apply)
Can you assign it?YesNo
Drives changes?Yes — edits trigger update/replaceNo — it reflects reality
Exampleinstance_type, tagsid, 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 console and terraform state show to explore available attributes before writing a brittle reference.
Last updated June 14, 2026
Was this helpful?