Terraform’s state is how it “remembers” what exists in your infrastructure so it can plan precise, minimal changes instead of blindly recreating resources. In this article, we’ll treat Terraform as a black box and learn how to inspect its memory using three key CLI tools: terraform show, the terraform state subcommands, and terraform output.


1. What Terraform State Actually Is

Terraform keeps a mapping between:

  • Your configuration (.tf files)
  • The real resources in your cloud provider (IDs, IPs, ARNs, etc.)

This mapping lives in a state file, usually terraform.tfstate, and often in a remote backend such as S3, Azure Blob Storage, or GCS for team use. The state includes every attribute of every managed resource, plus metadata used for things like dependency ordering and change detection.

Why you care:

  • Debugging: Is Terraform seeing the same thing you see in the console?
  • Refactoring: How do you rename resources without destroying them?
  • Automation: How do you feed outputs into CI/CD or other tools?

You should never hand-edit the state file; instead you use the CLI commands discussed below to read or safely modify it.


2. terraform show — Inspecting the Whole State or a Plan

Think of terraform show as “dump what Terraform currently knows” — it turns a state file or a saved plan into a human-readable or JSON view.

Core usage

# Show the current state snapshot (from the active backend)
terraform show

# Show a specific state file
terraform show path/to/terraform.tfstate

# Show a saved plan file
terraform show tfplan

# Machine-readable JSON for tooling
terraform show -json > plan.json
  • Without a file argument, terraform show prints the latest state snapshot from the active backend.
  • With a plan file, it describes the proposed actions and resulting state.
  • With -json, you get a structured document that external tools (e.g. CI, tests) can parse and validate.

Important: When using -json, sensitive values are printed in plain text; handle this carefully in pipelines and logs.

When to use terraform show

Use it when:

  • You want a global view: “What exactly is Terraform tracking right now?”
  • You want to inspect a plan artifact (plan -out tfplan) before approving it in CI.
  • You want to feed state or plan data into a tool (via -json) for policy checks, drift checks, or custom validation.

Conceptually, terraform show is read-only and holistic: it treats the state (or plan) as a whole, rather than individual resources.


3. terraform state — Fine-Grained State Inspection and Surgery

The terraform state command is a group of subcommands designed specifically to inspect and modify state without touching real infrastructure. This is the surgical toolkit you reach for when refactoring or repairing.

Key subcommands

Command What it does Typical use
terraform state list Lists all resource addresses in state “What is Terraform tracking?”
terraform state show ADDRESS Shows attributes of one resource Debugging one resource (IDs, IPs, tags, etc.)
terraform state mv SRC DEST Moves/renames a resource in state Refactors config without destroy/recreate
terraform state rm ADDRESS Removes a resource from state Stop managing a resource without deleting it
terraform state pull Prints raw state to stdout Backup, inspection, or external processing
terraform state push Uploads a local state file Restore/correct broken remote state (used rarely, carefully)

3.1 terraform state list

terraform state list
# e.g.
# aws_instance.web[0]
# aws_instance.web[1]
# aws_security_group.allow_ssh

This gives you the resource addresses Terraform knows about, optionally filtered by a prefix. It’s extremely useful when working with modules or count/for_each, because you can see the exact address Terraform expects.

3.2 terraform state show

terraform state show aws_instance.web[0]

This prints every attribute of that specific resource as seen in state — IDs, IPs, tags, relationships, and computed attributes. Semantically, it answers: “What does Terraform think this one resource looks like?”.

Use it when:

  • Debugging drift: console vs state mismatch.
  • Understanding complex resources: which subnet, which IAM role?
  • Checking data sources that were resolved at apply time.

Note the difference:

  • terraform show → everything (or full plan).
  • terraform state show ADDRESS → one resource only.

3.3 terraform state mv — Refactor Without Downtime

terraform state mv aws_instance.web aws_instance.app

If you simply rename the block in your .tf code, Terraform will plan to destroy the old resource and create a new one because it assumes they’re unrelated. state mv tells Terraform that the underlying resource is the same, you’re just changing the mapping.

This is critical for:

  • Renaming resources.
  • Moving resources into/out of modules.
  • Splitting a monolith configuration into multiple modules/workspaces.

3.4 terraform state rm — Stop Managing Without Deleting

terraform state rm aws_instance.legacy

This removes the resource from Terraform’s management while leaving it alive in your provider. Use this when decommissioning Terraform from part of your estate or when you temporarily need Terraform to “forget” something (e.g. migration to a different tool).

3.5 terraform state pull / push

These expose and manipulate the raw state blob:

terraform state pull > backup.tfstate
terraform state push backup.tfstate

They’re useful for backups or extremely rare recovery scenarios, but they’re dangerous if misused, so in practice you rely much more on list, show, mv, and rm.


4. terraform output — Consuming State Safely

terraform output reads output values defined in the root module and prints their values from the state file. It is the “official interface” for other systems (and humans) to consume selected bits of state without parsing the state file directly.

4.1 Defining outputs in configuration

In your root module:

output "instance_ips" {
  value = aws_instance.web[*].public_ip
}

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

output "db_connection_string" {
  value     = module.database.connection_string
  sensitive = true
}
  • Outputs are calculated after terraform apply and stored in state.
  • Only root module outputs are visible to terraform output; child module outputs must be re-exposed.

4.2 Using terraform output interactively

# Show all outputs for the root module
terraform output

# Show one specific output
terraform output lb_address

# Machine-readable JSON
terraform output -json

# Raw string (no quotes/newlines), perfect for scripts
terraform output -raw lb_address
  • With no arguments, it prints all root outputs.
  • With a NAME, it prints just that value.
  • -json gives a JSON object keyed by output name; can be piped into jq or similar tools.
  • -raw prints a bare string/number/boolean; ideal when exporting in shell scripts without extra quoting.

This is the idiomatic way to feed state into:

  • CI/CD pipelines (e.g. get ALB DNS for integration tests).
  • Other scripts (e.g. configure DNS records).
  • Other tools (e.g. Ansible inventory).

5. Putting It Together: A Simple Example

Below is a minimal, self-contained configuration you can run locally.

5.1. Prerequisites

  1. LocalStack running (Docker is typical):

    docker run --rm -it -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack

    LocalStack’s edge endpoint is exposed on http://localhost:4566 by default.

  2. Terraform installed (1.x).

5.2. Terraform Configuration Using LocalStack

Create a directory (for example tf-localstack-ec2) and within it create two files: versions.tf and main.tf.

versions.tf

Lock AWS provider to a version that is known to work well with LocalStack (4.x is a common choice):

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

main.tf

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    ec2 = "http://localhost:4566"
  }
}

resource "aws_instance" "web" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  tags = {
    Name = "tf-demo-web-localstack"
  }
}

output "web_public_ip" {
  value = aws_instance.web.public_ip
}

Notes:

  • The endpoints.ec2 block points the EC2 API at LocalStack’s edge endpoint.
  • Credentials are dummy; LocalStack doesn’t actually validate them.
  • The AMI ID is a placeholder; LocalStack typically does not require the AMI to exist, but EC2 support is limited and can hang for some combinations. For state/command learning it’s usually enough that Terraform “thinks” it created something.

5.3. How to Apply and Validate

From the directory containing these files:

  1. Initialize and apply

    terraform init
    terraform apply -auto-approve

    Terraform will talk to LocalStack instead of AWS because of the custom endpoint.

  2. Validate with show

    terraform show

    Confirm there is a aws_instance.web block with attributes populated from LocalStack’s response.

  3. Validate with state

    terraform state list
    # should include:
    # aws_instance.web
    
    terraform state show aws_instance.web

    This tells you what Terraform’s state holds for this specific resource address.

  4. Validate with output

    terraform output web_public_ip
    terraform output -raw web_public_ip

    For LocalStack, the public IP may be empty or synthetic depending on EC2 emulation level, but the command proves the wiring from resource → state → output.

5.4. Rationale for These Choices

  • We override only the EC2 endpoint to keep the example close to “real” AWS code while still talking to LocalStack.
  • We relax provider validations (skip_* flags) because LocalStack does not implement all AWS account/metadata APIs.

With this setup, you can safely experiment with terraform show, terraform state *, and terraform output on your laptop, without touching real AWS accounts or incurring cost.


6. Conceptual Summary: Which Command When?

Need Command Rationale
See everything Terraform knows terraform show Whole-state, read-only view (or a plan)
Inspect one resource deeply terraform state show ADDRESS Focused, per-resource state inspection
List all tracked resources terraform state list Discover resource addresses in state
Rename/move resources/modules terraform state mv Refactor mappings without downtime
Forget a resource but keep it alive terraform state rm Stop managing without deleting
Give other tools a clean interface terraform output / -json / -raw Official way to expose selected state data developer.

The underlying rationale is separation of concerns:

  • terraform showobservability of plans and state.
  • terraform stateprecise manipulation and inspection of state.
  • terraform outputcontrolled, stable API to state for humans and downstream systems.