A local-first Terraform visualization tool. Point it at any Terraform directory and get an interactive dependency graph, diagnostics, state drift detection, and multi-module navigation — all in the browser.
- Dependency graph — interactive React Flow graph of resources, data sources, and modules with auto-layout. Expand any module node inline to reveal its child resources, data sources, and sub-modules without leaving the current view
- Diagnostics engine — detects dependency cycles, unused variables, missing tags, missing descriptions, provider version pins, sensitive naming, high blast-radius resources, and orphaned state entries
- State drift detection — reads remote state (S3, Azure Blob Storage, and GCS backends) and compares with HCL, flagging resources as in-sync, drifted, not-in-state, or orphaned. Modules show an aggregate indicator: green (all children in sync), light yellow (partially drifted), or orange (all children drifted)
- Multi-module navigation — discover and jump into nested modules; see which internal resources participate in cross-module cycles
- Live reload — watches the filesystem for
.tfchanges and pushes updates over WebSocket
| Tool | Version |
|---|---|
| Go | 1.26+ |
| Node.js | 20+ (see web/.nvmrc) |
| npm | 10+ |
| AWS CLI | optional, for S3 state reading |
| Azure CLI | optional, for Azure Blob state reading |
| gcloud CLI | optional, for GCS state reading |
# Build everything (frontend + backend) for your current OS/arch
make build
# Run against a Terraform directory
./tfmap /path/to/terraform/project
# Or just run tfmap — an interactive directory picker will appear
./tfmap
# Or run from source
make run DIR=/path/to/terraform/projectThe frontend is embedded into the binary at compile time via go:embed, so the resulting tfmap binary is fully self-contained — copy it anywhere and it works.
When run without a path argument, tfmap opens a native OS folder picker dialog so you can browse to your Terraform project directory.
The UI opens in a native tfmap window using the OS's built-in webview (WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux) — closing the window quits tfmap. If the native webview isn't available (e.g. WebKitGTK not installed on Linux), tfmap falls back to a Chromium app-mode window, then to a regular tab in your default browser; in fallback mode the server keeps running until Ctrl-C. The server listens at http://127.0.0.1:<port> (a random available port is chosen by default). Pass --browser if you prefer a regular browser tab.
Build for all supported platforms at once:
make build-allThis produces binaries in dist/:
| File | Platform |
|---|---|
dist/tfmap-darwin-arm64 |
macOS Apple Silicon |
dist/tfmap-darwin-amd64 |
macOS Intel |
dist/tfmap-linux-amd64 |
Linux 64-bit |
dist/tfmap-linux-386 |
Linux 32-bit |
dist/tfmap-windows-amd64.exe |
Windows 64-bit |
Or build for a single target:
make build-linux-amd64
make build-darwin-arm64
make build-windows-amd64| Flag | Short | Default | Description |
|---|---|---|---|
--port |
-p |
0 (random) |
Port to serve the UI on |
--no-browser |
false |
Don't open the UI automatically | |
--browser |
false |
Open the UI in a regular browser tab instead of a dedicated app window | |
--no-state |
false |
Skip state reading entirely | |
--aws-profile |
AWS profile for S3 state reading |
Supported state backends: local, S3, Azure Blob Storage (azurerm), Google Cloud Storage (gcs). Azure uses DefaultAzureCredential (run az login), GCS uses Application Default Credentials (run gcloud auth application-default login).
# Use a specific port
tfmap -p 3000 ./infra
# Skip state, just visualize the HCL
tfmap --no-state ./modules/networking
# Use a named AWS profile for S3 state
tfmap --aws-profile production ./envs/prod
# Azure backend — authenticate with az login first
az login
tfmap ./envs/prod
# GCS backend — authenticate with gcloud first
gcloud auth application-default login
tfmap ./envs/prodtfmap/
├── cmd/ # CLI entrypoint (Cobra)
├── internal/
│ ├── diagnostics/ # Diagnostic rules and cycle detection
│ ├── model/ # Shared data model (Project, Resource, etc.)
│ ├── parser/ # HCL parser (hclsyntax AST)
│ ├── server/ # HTTP + WebSocket server
│ ├── state/ # Terraform state reader (local, S3, Azure, GCS)
│ └── watcher/ # Filesystem watcher (fsnotify)
├── web/ # React SPA (Vite + TypeScript + Tailwind)
│ └── src/
│ ├── components/ # TreeExplorer, GraphView, DetailPanel, etc.
│ ├── hooks/ # useProject (WebSocket + fetch)
│ ├── utils/ # Shared utilities (module resolution, etc.)
│ └── types.ts # TypeScript types mirroring Go model
├── main.go # Entrypoint, passes embedded FS to CLI
├── embed.go # go:embed directive for web/dist
├── Makefile
└── README.md
Run the Go backend and Vite dev server separately for hot-reload:
# Terminal 1: backend on port 8080
make dev-backend
# Terminal 2: frontend with proxy to :8080
make dev-frontendThe Vite dev server proxies /api and /ws to 127.0.0.1:8080.
# Run all Go tests
make test
# Run tests with verbose output
make test-verbose
# Run only diagnostics tests
go test ./internal/diagnostics/ -vNote: Use
make testrather thango test ./...directly — the Makefile ensuresweb/distexists, which is required by thego:embeddirective in the root package.
# Run all linters
make lint
# Or individually
go vet ./...
cd web && npm run lint┌─────────────┐ ┌──────────┐ ┌─────────────┐
│ .tf files │────▶│ Parser │────▶│ Project │
└─────────────┘ └──────────┘ │ (model) │
└──────┬──────┘
┌─────────────────────┐ │
│ State (S3/Azure/GCS)│─────────────▶ CompareWithState
└─────────────────────┘ │
┌──────▼──────┐
│ Diagnostics │
└──────┬──────┘
│
┌────────────────────────────▼────────────────┐
│ HTTP + WebSocket Server │
│ GET /api/project WS /ws (live reload) │
└────────────────────────────┬────────────────┘
│
┌──────────────▼──────────────┐
│ React SPA (browser) │
│ Graph ─ Tree ─ Diagnostics │
└─────────────────────────────┘
Data flow: Parser reads .tf files → builds a Project model → state reader enriches with drift info → diagnostics engine analyzes → server serves over HTTP/WS → React app renders the graph, tree, and diagnostic panels. The filesystem watcher re-triggers the pipeline on changes.
The React SPA is embedded into the Go binary at build time (go:embed), so the server serves it directly from memory. During development, the server falls back to the web/dist directory on disk, allowing the Vite dev server to handle frontend hot-reload via proxy.
- InfraMap — generates static infrastructure diagrams from Terraform state/HCL with provider-aware filtering. A good fit if you want exportable SVG/DOT diagrams rather than an interactive browser UI.
This project is licensed under the MIT License. See LICENSE for details.
Contributions are welcome. See CONTRIBUTING.md for guidelines.
