Why the graph exists
Terraform is declarative. You write "there must be a bucket, there must be an EC2 instance whose user_data references that bucket," and Terraform works out the order: bucket first, EC2 second.
That "works it out" part happens inside a directed acyclic graph (DAG).
Each resource is a node. Each reference like aws_s3_bucket.demo.arn in
another resource's code is an edge pointing from consumer to source.
Terraform builds this graph, finds a topological sort, and applies
resources in that order.
terraform graph exposes this graph. It is a window into Terraform's
internals.
Basic output
cd ~/myproject
terraform graph
Stdout contains text in Graphviz dot format:
digraph {compound = "true"
newrank = "true"
subgraph "root" {"[root] aws_s3_bucket.demo (expand)" [label = "aws_s3_bucket.demo", shape = "box"]
"[root] random_id.suffix (expand)" [label = "random_id.suffix", shape = "box"]
"[root] aws_s3_bucket.demo (expand)" -> "[root] random_id.suffix (expand)"
...
}
}
This text is not meant to be read directly. To view it you need to convert it to an image.
Converting to PNG/SVG
If graphviz is installed:
terraform graph | dot -Tpng > graph.png
terraform graph | dot -Tsvg > graph.svg
Open the image and you will see resource boxes with arrows. An arrow from
aws_s3_bucket.demo to random_id.suffix means "the bucket depends on
random_id."
On Linux/macOS:
sudo apt install graphviz # Ubuntu/Debian
brew install graphviz # macOS
On Windows: install Graphviz and add
dot.exe to PATH.
Graph modes
By default terraform graph shows the plan graph (what will happen on
apply). Other modes are available:
terraform graph -type=plan # default, for the upcoming apply
terraform graph -type=plan-destroy # for terraform destroy
terraform graph -type=apply # post-apply: what has already been created
Most of the time plan is what you want. The apply type shows only
nodes that were actually changed.
What to look for in the graph
1. Isolated nodes with no edges
These are resources with no dependencies. They apply first, in parallel. If you expected a resource to wait for another but there is no edge, the dependency is not declared.
Fix it by referencing an attribute (implicit dependency) or by adding
depends_on = [...] (explicit). See tf-depends-on.
2. Cycles
If resource A depends on B and B depends on A, the graph is cyclic. That is not a valid DAG, and Terraform will fail:
Error: Cycle: aws_iam_role.a, aws_iam_policy.b
Cycles are visible in the graph as two edges between the same pair of nodes pointing in opposite directions. See tf-common-errors for how to resolve cycles.
3. Long chains
If 10 resources form a sequential chain (a → b → c → ...),
Terraform's parallelism is lost. An apply that could take 1 minute takes
10. Sometimes this is unavoidable (RDS must exist before the EC2 instance
that connects to it). Other times it is a side-effect of unnecessary
depends_on entries.
Using jq for analysis
Terraform 1.4+ added terraform graph -draw-cycles to highlight cycles.
For machine-readable analysis, JSON via terraform show -json is more
convenient:
terraform plan -out=plan.bin
terraform show -json plan.bin | jq '.resource_changes[].address'
This gives you the address of every resource that will change. From there you can build reports or feed the data into other tools.
Rover: an alternative
For interactive visualization there is Rover, which starts a local web server with a clickable graph.
rover -tfPath /path/to/terraform-binary
For large projects (50+ resources) this is far more usable than a dot PNG.
Pitfalls
-
The output of
terraform graphis large even for small projects. Every provider, local value, and data source is a node too. Do not be alarmed by 50 nodes for 5 resources; most of them are internal. -
DOT format does not always render cleanly. Large projects produce spaghetti on a PNG. Alternatives: Rover (interactive), Inframap (simplified cloud graph), or
terraform graph -type=planwith manualgrepfiltering by resource type. -
The graph depends on the current state. If state is empty, the graph shows "add everything." If some resources already exist, only new ones appear. To see the full graph of the current project, run
terraform refreshfirst. -
-type=plan-destroyshows the reverse order. Destroy goes from leaves to root: dependents first, then their dependencies. Use this mode when you want to understand the teardown sequence. -
-draw-cycleswas added in TF 1.4. On older versions you must find cycles by eye in the DOT output or withgrep '->'.