Terraform graph visualization, the short version: run terraform graph | dot -Tsvg > graph.svg to render your dependency graph, then pipe it through tred to cut the noise. That gets you a correct build order. It doesn't get you an architecture diagram, because Terraform's arrows mean "depends on", not "sends traffic to".
Everyone tries it once. You run the command, open the SVG, and get a plate of boxes and lines that reads like a subway map drawn during an earthquake.
The command isn't broken. You're asking it a question it was never built to answer. Here's what each Terraform view really tells you, how to make the output readable, and how to get from there to a diagram a reviewer can trust.
What terraform graph visualization actually shows
By default, terraform graph outputs a simplified graph of the dependency ordering between resource and data blocks. That's the graph Terraform uses to decide what to do first, not a map of your system.
Terraform's dependency graph docs explain where the edges come from: references between resources, plus explicit depends_on. So an arrow points from the thing doing the referencing to the thing being referenced.
That one detail breaks most first attempts. A listener references its load balancer, so the arrow runs listener to load balancer. Traffic flows the other way. Read the graph as a network diagram and it's confidently, beautifully wrong.
Terraform gives you four separate views of the truth, and each answers a different question:
| Question | Where to look | Command | What it can't tell you |
|---|---|---|---|
| What depends on what? | Configuration | terraform graph | Traffic, trust zones, anything unmanaged |
| What does Terraform think it manages? | State | terraform state list | Whether reality still matches |
| What will this change do? | Plan | terraform show -json tfplan | Whether the design is any good |
| What changed outside Terraform? | Refresh-only plan | terraform plan -refresh-only | Resources Terraform has never tracked |
Pick the view that matches your question and half the pain disappears.
→ Explore your Terraform modules with architecture context in IaC Orchestration
Terraform graph to SVG: make the output readable
The basic version takes two commands. You'll need Graphviz installed, because Terraform prints DOT text rather than an image:
terraform init
terraform graph | dot -Tsvg > graph.svg
Now the upgrades. tred is a Graphviz filter that does transitive reduction: if A depends on B, and B depends on C, a direct A to C edge is redundant, so it goes. On a real config this removes a surprising amount of visual noise. Left-to-right layouts also tend to suit long resource names:
terraform graph | tred | dot -Tsvg -Grankdir=LR > graph-clean.svg
Keep one caveat in mind: tred hides redundant dependency edges, so use the reduced picture for reading and the full output for debugging.
A few more flags are worth knowing:
| Goal | Command |
|---|---|
| See the detailed plan graph, including runtime internals | terraform graph -type=plan | dot -Tpng > plan.png |
| Draw the graph for one saved plan | terraform graph -plan=tfplan | dot -Tsvg > apply.svg |
| Debug a cycle error | terraform graph -type=plan -draw-cycles | dot -Tsvg > cycles.svg |
| Pass variables | terraform graph -var-file=prod.tfvars | dot -Tsvg > prod.svg |
Two gotchas. -draw-cycles only works with a real graph type, so it needs -type. And the detailed types expose implementation details of the Terraform runtime, so a bigger graph isn't automatically a more useful one. For everyday reading, stick with the default.
For a fast read on a module you've just inherited, paste the HCL into the Terraform Module Visualizer. It runs in your browser and returns counts, a resource inventory and a starter Mermaid topology. Treat it as a scoping tool: it reads the text you paste, so it won't resolve variables, count or remote modules, and its topology isn't a dependency graph.
→ Paste your HCL into the free Terraform Module Visualizer
How to visualize Terraform state and plans
The dependency graph can't show you what's deployed or what's about to change. For that, go to state and plan data.
Visualize Terraform state
Start with terraform state list, which prints one resource address per line. Root resources come first, deeper modules last, and you can filter by module:
terraform state list
terraform state list module.network
It's simple, and for "what does this workspace own?" it often beats any picture. Need structured data for a script? terraform show -json gives you the latest state snapshot as JSON.
Terraform plan visualization with jq
Before a risky apply, reviewers want a list of what's changing and how. Save the plan, convert it, then ask for exactly that:
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
jq -r '.resource_changes[]
| select(.mode == "managed")
| "\(.change.actions | join("+"))\t\(.address)"' plan.json
You'll get one line per resource instance:
no-op aws_lb.public
update module.net.aws_subnet.a
delete+create aws_db_instance.main
That last line is the one to circle in red. The JSON output format represents a replacement as an action pair, either delete then create or the reverse, so scanning for delete catches every destructive change.
The same JSON can also build a smaller, reference-only graph of your root module:
jq -r '
[ .configuration.root_module.resources[]
| .address as $from
| .expressions | .. | .references? // empty | .[]
| select(test("^(data\\.)?[a-z0-9_-]+\\.[a-zA-Z0-9_-]+$"))
| select(test("^(var|local|path|terraform|each|count|self)\\.") | not)
| " \"\($from)\" -> \"\(.)\";"
] | unique | "digraph refs {", .[], "}"
' plan.json | dot -Tsvg > refs.svg
This drops variables and locals, keeps module calls as single nodes, and only walks the root module. It's a one-off reading aid, not production tooling: the docs tell callers to stick to string equality on references, because the syntax may be extended. Don't build CI on that regex.
Heads up: saved plans can hold sensitive data in cleartext, even when the terminal output hides it.
terraform show -jsonalso prints sensitive state values in plain text. Don't pasteplan.jsoninto a random web tool or leave it lying around as a public CI artifact.
→ Bring Terraform and state into a tracked import with IaC Import
Worked example: the arrow your graph will never draw
Here's why a clean graph still isn't architecture. Take a standard three-tier app, trimmed to the interesting bits:
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.public.arn
port = 443
protocol = "HTTPS"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
resource "aws_vpc_security_group_ingress_rule" "db_from_app" {
security_group_id = aws_security_group.db.id
referenced_security_group_id = aws_security_group.app.id
from_port = 5432
to_port = 5432
ip_protocol = "tcp"
}
Compare what the graph says with what a reviewer needs to know:
| Reviewer's question | What the graph shows | What the diagram must show |
|---|---|---|
| How do users reach the app? | Listener points at the load balancer and target group | User to load balancer on 443, then to the app tier |
| Does the app talk to the database? | Both security groups connect through a rule node | App to database on 5432, labelled as allowed |
| Which tier is public? | Nothing, subnets are just nodes | A boundary around the public tier |
| What about the payment provider? | Nothing, it isn't a Terraform resource | An external system with a labelled egress path |
The database row is the sneaky one. If the app reads its database hostname from a secret at runtime, there may be no Terraform reference between the app and the database at all. The dependency that matters most to your 3 a.m. self might not exist in the graph. Terraform's own depends_on docs acknowledge the wider point: some dependencies are hidden and can't be inferred from configuration.
→ Trace a resource back to its file, module and revision with IaC Explorer
Terraform visualization tools compared
You don't have to hand-roll everything. Here's an honest look at the common options. Check each project's recent activity before standardizing on it:
| Tool | Input | Good for | Watch out for |
|---|---|---|---|
terraform graph and Graphviz | Your configuration | Exact ordering, debugging cycles | Noisy, no boundaries or flows |
| Rover (open source) | Config and plan, or plan JSON | Interactive local exploration, SVG export | Runs init and plan by default, so it needs the same access and variables |
| InfraMap (open source) | State or HCL | Simplified graphs, with provider logic for AWS, Google Cloud, Azure, OpenStack and FlexibleEngine | Other providers get all resources with no simplification |
| Terraform Module Visualizer | Pasted HCL | Quick inventory, no install | Text scan, not a resolved dependency graph |
| Architecto IaC features | Connected repos and imports | Keeping module, resource and architecture context together | Bigger setup than a one-off command |
Our take: use the CLI to debug, a local viewer to explore, and a proper diagram to communicate. Trouble starts when one artifact tries to do all three. If you're weighing code-generated pictures against hand-drawn ones, see architecture diagram as code vs drag and drop.
→ Compare Architecto and Cloudcraft for infrastructure diagrams
Generate an architecture diagram from Terraform code
Want to visualize Terraform infrastructure as a diagram people will actually use? Generated output is your inventory, not your finished picture. Five steps get you from one to the other.
- Pick one root module and one environment. A diagram of "all our Terraform" answers nothing. Name the workspace in the caption.
- Collapse resources into concepts. A load balancer, listener and target group become one "Load balancer" box. Good module boundaries make this much easier.
- Draw boundaries. Add accounts, networks, tiers and trust zones. The graph won't draw these for you.
- Replace dependency arrows with flows. Redraw every line as "who calls whom", then add the protocol and port. Use the security group rules as evidence.
- Add what Terraform doesn't know. Include users, third-party APIs, manually created resources and anything owned by another team.
Before you publish it, run this checklist:
- Caption names the workspace, environment and commit
- Every arrow means traffic or data flow, not build order
- Public, private and external zones are visible
- Every data store has a labelled path to its callers
- External dependencies are on the page
- Unmanaged resources are marked as unmanaged
- A named owner will update it when the code changes
Inherited the whole repo rather than one module? Our guide to reverse engineering architecture from code walks through the wider process.
→ Turn your inventory into an editable first draft with Architect AI
Keep your Terraform diagram honest as things change
A diagram generated from code can still be wrong if someone clicked around in the console last Tuesday. Your config, state and real infrastructure can all disagree.
The check is built in. A refresh-only plan compares each resource against real infrastructure and reports the differences without changing anything:
terraform plan -refresh-only -detailed-exitcode
Per the plan docs, exit code 0 means no changes, 1 means an error and 2 means changes were found. That makes it easy to schedule. If you have plan JSON, resource_drift holds the differences Terraform found between the prior saved state and the latest one.
The drift control explainer covers the wider process. One limit to remember: this only checks what Terraform already tracks. A database someone created by hand won't appear, because it was never in state.
→ Compare architecture intent with deployed reality using Drift Detector
Key takeaways
terraform graphshows dependency ordering. Treat every arrow as "depends on", never as traffic.tredand a left-to-right layout make the output far easier to read.- Use state for "what do we manage?", plan JSON for "what's changing?" and refresh-only plans for "what drifted?".
- Your most important runtime dependency may not exist in the graph at all.
- Generated output is the inventory. Boundaries, flows and external systems are the architecture.
- Plan files and JSON output can expose sensitive values, so handle them like secrets.
→ Run your diagram past the Architecture Review Checklist Builder
FAQ
How do I visualize a Terraform graph?
Run terraform init, then pipe terraform graph into Graphviz with terraform graph | dot -Tsvg > graph.svg. The command prints DOT text, not an image, so Graphviz does the rendering. Add tred before dot to remove redundant edges on larger configurations.
Can I generate an architecture diagram from Terraform code?
Partly. Terraform can give you resources, modules and dependency order, but an architecture diagram also needs traffic flows, trust boundaries and systems Terraform doesn't manage. Use the code as the inventory, then add those layers yourself or in a tool that keeps the code as context.
How do I visualize Terraform state?
Use terraform state list to see managed resources, filtered by module when needed, and terraform show -json to get the state as JSON for scripts or a third-party viewer. State shows what Terraform last recorded, so run terraform plan -refresh-only if you need to check it against real infrastructure.
What is the difference between terraform graph and terraform plan?
terraform graph shows dependency ordering, while terraform plan shows the actions needed to reach your configuration. You can combine them: terraform plan -out=tfplan followed by terraform graph -plan=tfplan draws the graph for applying that specific plan.
Is it safe to share Terraform plan JSON with a visualization tool?
Treat it as sensitive. Terraform's docs warn that sensitive data is saved in cleartext in plan files, and that terraform show -json displays sensitive state values in plain text. Review where the file goes before uploading it anywhere or storing it as a CI artifact.
→ Keep Terraform and architecture in one place with IaC Orchestration
Stop shipping spaghetti to your design review
terraform graph is a great debugger and a terrible architecture diagram. Respect both facts and you'll stop losing afternoons to DOT files.
Start small: render the graph, clean it up, then spend your time on what the machine can't infer, like flows, boundaries and the stuff Terraform has never heard of. Architecto gives that diagram a home next to your infrastructure code.