Skip to main content
RunBook Academy

TerraformIX · State: The Core Production ConceptProduction Terraform

Exploring the State Safely

Foundation⏱ ~10 minbash

What you'll learn

  • Use terraform state list to enumerate addresses
  • Use terraform state show to inspect a single resource
  • Use terraform state pull to fetch the raw JSON for tooling
  • Use terraform output to read declared output values
  • Recognise the read-only commands and never edit the state by hand

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.

The single most useful state-discipline habit: before any mutating operation, run the read-only commands. state list tells you what Terraform thinks is there. state show tells you what Terraform knows about one resource. state pull gives you the raw JSON for tooling. terraform output reads declared outputs. None of them change anything. All of them inform you before you make a change.

terraform state list

The basic enumeration:

terraform state list

Output:

aws_instance.web
aws_lb.main
aws_security_group.web
aws_subnet.public[0]
aws_subnet.public[1]
aws_subnet.public[2]
data.aws_ami.ubuntu
module.network.aws_route_table.rt["us-east-1a"]
module.network.aws_route_table.rt["us-east-1b"]
module.network.aws_route_table.rt["us-east-1c"]
module.network.aws_vpc.main

Every line is a complete resource address. Compare this list against the resource blocks in the configuration. The two should match. A resource in configuration but not in the list is a plan error. A resource in the list but not in configuration is an orphan — a candidate for state rm or for adding back to configuration.

The list accepts an address pattern to filter:

terraform state list module.network
terraform state list 'aws_subnet.public[*]'

The pattern syntax mirrors resource addressing.

terraform state show

For one resource, the full state record formatted for humans:

terraform state show 'aws_instance.web'

Output:

# aws_instance.web:
resource "aws_instance" "web" {
    ami                          = "ami-0c55b159cbfafe1f0"
    arn                          = "arn:aws:ec2:us-east-1:123456789012:instance/i-0a1b2c3d4e5f6a7b8"
    associate_public_ip_address  = false
    availability_zone            = "us-east-1a"
    id                           = "i-0a1b2c3d4e5f6a7b8"
    instance_type                = "t3.medium"
    subnet_id                    = "subnet-0aaa"
    tags                         = {
        "Environment" = "production"
        "Name"        = "web"
    }
    ...
}

The id is the real-world identifier. The other attributes are the last-known values from the state cache. If the values look stale (e.g. a tag that was added yesterday is missing), the state has not been refreshed against the provider API.

state show does not refresh. It reads what is in state. To see what the provider API currently returns, run terraform plan -refresh-only or terraform apply -refresh-only.

terraform state pull

The raw state JSON:

terraform state pull

Output is the full document. Pipe through jq for navigation:

terraform state pull | jq '.resources[] | select(.type == "aws_instance") | .name'

Output:

"web"

state pull is the canonical way to feed the state into other tools: drift detection, CMDB sync, audit pipelines. The output is the exact bytes that the backend holds; nothing is reformatted.

The -json flag on most Terraform commands produces machine-readable output. Combine with jq for production tooling:

terraform show -json | jq '.resource_changes[] | {address: .address, action: .change.actions[0]}'

terraform output

Declared output values:

terraform output

Output:

vpc_id = "vpc-0123456789abcdef0"
subnet_ids = [
  "subnet-0aaa",
  "subnet-0bbb",
]
database_endpoint = "db.example.internal"

For a single output:

terraform output -raw vpc_id

Output:

vpc-0123456789abcdef0

The -raw flag strips quotes; useful in shell pipelines:

VPC_ID=$(terraform output -raw vpc_id)
aws ec2 describe-vpcs --vpc-ids "$VPC_ID"

Outputs marked sensitive = true print as (sensitive value) in the CLI. The -raw flag still works — it prints the value but warns. The security lessons cover the discipline.

Combining read-only commands

A production pre-flight check before any mutating operation:

terraform state list | wc -l                                  # resource count
terraform state pull | jq '.serial'                           # current serial
terraform state pull | jq '.lineage'                          # lineage
terraform output | wc -l                                      # output count
terraform plan -refresh-only -out=/tmp/refresh.tfplan        # drift check
terraform show -json /tmp/refresh.tfplan | jq '.resource_changes | length'

If all of these match expectations, proceed. If any are unexpected, investigate before applying.

Validation

READ-ONLY

Confirm the read-only commands work end to end:

terraform state list > /tmp/state-list.txt
wc -l /tmp/state-list.txt
terraform state pull | jq '.serial' > /tmp/serial.txt
cat /tmp/serial.txt
terraform output vpc_id

Output (illustrative):

142 /tmp/state-list.txt
27
"vpc-0123456789abcdef0"

A consistent resource count, serial that matches the last known value, and outputs that match expectations are the three signals that the state backend is reachable and the read-only path is intact.

Production failure modes

Symptom: terraform state list returns an empty list. Cause: the state is reachable but contains no resources. Confirm with terraform plan — an empty plan confirms there is no work to do. If the configuration declares resources, investigate why state is empty (perhaps a fresh backend).

Symptom: terraform state show returns “Resource not found in state”. Cause: the address is misspelled, or the resource is in a different module path. Use state list to find the correct address.

Symptom: terraform state pull returns malformed JSON. Cause: the state has been corrupted (manual edit, partial write, backend integrity failure). Stop. Restore from the versioned backup. Do not attempt to fix the JSON.

Symptom: terraform output prints “(sensitive value)” but the pipeline needs the actual value. Cause: the output is marked sensitive = true. The -raw flag returns the value but emits a warning. The discipline: use -raw and pass the value directly to the next command; never echo to a log.

Symptom: terraform output -json returns an empty object. Cause: no outputs are declared. Confirm with the configuration.

Recovery

The read-only commands do not need recovery because they do not mutate state. The recovery pattern when read-only output looks wrong is to inspect the state from a different angle:

  1. terraform state pull > current.json — confirm the JSON is valid.
  2. terraform state list — confirm the resource count.
  3. terraform plan — confirm the planned diff matches expectation.
  4. If any of these are inconsistent, restore state from the versioned backup.

What comes next

The next part covers state operations: the imperative and declarative commands for moving resources, replacing providers, and importing existing infrastructure.

Verification

  • You can list every resource address in state with terraform state list.
  • You can inspect a single resource with terraform state show and read the id attribute.
  • You can pull the raw state JSON and navigate it with jq.
  • You can read declared outputs with terraform output and handle sensitive = true values correctly.

Knowledge check · 7 questions

  1. Q1. Which command prints the raw state JSON?

  2. Q2. Which flag on terraform output strips quotes from a single value for use in a shell pipeline?

  3. Q3. `terraform state show` refreshes the state from the provider API before showing the resource.

  4. Q4. Which command lists every resource address in state, including module paths and instance keys?

  5. Q5. Which of the following are read-only operations on state? (Select all that apply.)

  6. Q6. A `terraform state pull | jq` pipeline returns a serial that is two higher than expected but no apply has run in the last day. What does this mean?

  7. Q7. An operator wants to verify which real-world AWS resource IDs are managed by Terraform before applying a change. Which read-only commands provide that?

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