Skip to content
Infrastructure as Code iac workflow 4 min read

terraform graph

Terraform builds an internal dependency graph from your configuration and state, then walks it to decide the order in which resources are created, updated, or destroyed. The terraform graph command exposes that graph as DOT — a plain-text description you can render into a diagram. This is invaluable for understanding implicit dependencies, debugging unexpected ordering, and communicating architecture to your team. OpenTofu ships the same command (tofu graph) with identical behavior.

How the dependency graph works

Terraform never relies on the order resources appear in your .tf files. Instead, it infers edges from references: when one resource interpolates an attribute of another, an edge is added so the referenced resource is processed first. This is why explicit depends_on is only needed when a dependency exists outside of Terraform’s visibility.

Consider a small VPC stack:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "main"
  }
}

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"
}

resource "aws_instance" "web" {
  ami           = "ami-0c02fb55956c7d316"
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public.id
}

Because aws_subnet.public references aws_vpc.main.id, and aws_instance.web references aws_subnet.public.id, Terraform knows it must create the VPC, then the subnet, then the instance — and destroy them in reverse.

Generating the DOT output

Run the command from your initialized working directory. It prints the graph to standard output in DOT format.

terraform graph

Output:

digraph {
	compound = "true"
	newrank = "true"
	subgraph "root" {
		"[root] aws_instance.web (expand)" [label = "aws_instance.web", shape = "box"]
		"[root] aws_subnet.public (expand)" [label = "aws_subnet.public", shape = "box"]
		"[root] aws_vpc.main (expand)" [label = "aws_vpc.main", shape = "box"]
		"[root] aws_instance.web (expand)" -> "[root] aws_subnet.public (expand)"
		"[root] aws_subnet.public (expand)" -> "[root] aws_vpc.main (expand)"
		"[root] provider[\"registry.terraform.io/hashicorp/aws\"]" -> "[root] aws_vpc.main (expand)"
	}
}

Each arrow A -> B means “A depends on B,” so B is created before A.

Rendering the graph with Graphviz

The raw DOT text is hard to read, so pipe it through Graphviz’s dot tool to produce an image. Install Graphviz first (brew install graphviz, apt install graphviz, or choco install graphviz), then:

terraform graph | dot -Tsvg > graph.svg

You can target other formats just as easily:

terraform graph | dot -Tpng -o graph.png
terraform graph | dot -Tpdf -o graph.pdf

Tip: For large configurations the default graph can be dense and unreadable. Render to SVG (vector, zoomable) rather than PNG, and consider scoping the graph with -target on the underlying plan, or splitting the configuration into smaller modules.

Choosing what the graph shows

By default terraform graph reflects the plan-time graph. Flags let you select a different operation or a more detailed view.

FlagDescription
-type=planGraph for a plan (the default when state or no plan is present).
-type=applyGraph for an apply operation.
-type=plan-destroyGraph for a destroy plan, showing teardown ordering.
-type=apply with -plan=FILEGraph from a saved plan file.
-draw-cyclesHighlight dependency cycles in red, which is useful when an apply fails with a cycle error.
-module-depth=nLimit how deeply module internals are expanded (-1 for unlimited).

To inspect destroy ordering — handy before running terraform destroy on a production stack:

terraform graph -type=plan-destroy | dot -Tsvg > destroy.svg

To diagnose a Cycle: error, add -draw-cycles so the offending edges stand out:

terraform graph -draw-cycles | dot -Tsvg > cycle.svg

Graphing from a saved plan

If you want the exact graph for a specific planned change, save the plan first and feed it back in. This guarantees the graph matches what terraform apply will execute.

terraform plan -out=tfplan
terraform graph -plan=tfplan | dot -Tsvg > planned.svg

Output:

digraph {
	compound = "true"
	newrank = "true"
	subgraph "root" {
		"[root] aws_instance.web" [label = "aws_instance.web", shape = "box"]
		"[root] aws_subnet.public" [label = "aws_subnet.public", shape = "box"]
		"[root] aws_instance.web" -> "[root] aws_subnet.public"
	}
}

Best practices

  • Render to SVG and commit a generated diagram to your repo’s docs/ folder so reviewers can see architecture changes alongside code changes.
  • Use terraform graph -type=plan-destroy before destroying shared infrastructure to confirm nothing critical is torn down early.
  • Reach for -draw-cycles the moment Terraform reports a cycle error — it pinpoints the loop far faster than reading raw DOT.
  • Prefer implicit dependencies (attribute references) over depends_on; the graph is most accurate when Terraform infers edges itself.
  • Keep modules small and focused so individual graphs stay legible; a single giant graph signals a configuration that should be split.
  • Remember the graph reflects the current configuration and state, so run terraform init and ensure state is fresh before trusting the output.
Last updated June 14, 2026
Was this helpful?