terraken

Everything you can know about a change, without asking permission

Terraken is a free, open-source command-line tool for Terraform and OpenTofu. Hand it a plan and it tells you what the change actually does, ranked by how much damage it can do. It takes a file and nothing else: no credentials, no network, no apply, and no attribute value in the output.

Install

go install github.com/dbhq-uk/terraken/cmd/terraken@latest

GoMITLinux and macOS, on amd64 and arm64

terraken plan.json
terraken  1 finding  terraform 1.9.8
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CRITICAL ────────────────────────────────────────────────────  1

  azurerm_postgresql_flexible_server.main
  destroy and create
holds data, so destroying it loses that data
an attribute changed that cannot be updated in place
forces replacement   zone

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1 critical

Reading the report

One section per severity present, most severe first, and none at all for a severity with nothing in it. The report is set to the width of your terminal, between 60 and 100 columns.

The finding above is critical rather than high for one reason: the resource type holds data, so destroying it loses that data. It is the only escalation in the tool, and it applies to destruction alone.

Notice what is not there. The attribute that forced the replacement is named as zone, and neither its old value nor its new one appears, because no value ever does.

What it finds

  • What this change destroys, ranked, with the ones that lose data first
  • Why a resource is being replaced, using Terraform's own stated reason
  • Which attribute forced the replacement, by name
  • Renames that forgot a moved block: a destroy and a create that look like the same resource, which is how a refactor quietly destroys a database it meant to keep
  • Attributes whose before and after are the same value written differently: a reshuffled list, a JSON policy whose keys moved, a re-indented heredoc, a port that came back as a string, a null that became an empty list
  • When that is the whole of a resource's change, said out loud, which is the fastest way to clear an update in place that is really nothing
  • What cannot be known until apply, so you can see which claims about this change are unverifiable in review

Each of those is a statement of fact rather than a verdict. Order is significant for a container command, whitespace is significant in a script, and a tool that called either one harmless would eventually be confidently wrong about somebody's production change. Terraken says what kind of difference it found and leaves the call to you.

Three things it will not do

  • It takes a file, and runs nothing

    Terraken reads a plan file, or the same JSON piped in on standard input. It never runs terraform, never reads a cloud credential, never makes a network call and never applies anything. The only file it writes is the one you name with --out, created mode 0600 because the report lists every resource in the plan.

  • It never prints an attribute's value, in any format

    Not masked, not redacted, not truncated. Values are not in the output at all. Masking depends on Terraform having marked a value sensitive, and that marking is best-effort: a live credential was found in a real plan that Terraform had not marked. Paths, counts, levels and Terraken's own sentences are all it will ever show you.

  • It is deterministic, with no model in the loop

    The same plan always produces the same verdict. Nothing is sent anywhere, there is no model to talk you round, and no ranking that cannot be read straight off the plan. Findings are sorted most severe first with ties broken on the resource address, so two runs of the same plan are byte for byte identical.

Not yet built

Where this is going

Terraken ships one command today and the rest of this page describes it accurately. This is the rest of the plan, and each item links to the issue tracking it. What makes the list worth reading is not its length but its edges: every capability here was picked because it can be built without giving up one of the three contracts above. Roughly a hundred things a Terraform tool could do were considered; about ten survive that test.

  • Blast radius(external site)#3

    What else depends on each resource being destroyed. The plan file already carries the dependency graph: its configuration object holds an expressions map, and every expression exposes the addresses it references, already unwrapped. So the question normally answered by terraform graph, or by a hosted service holding your cloud credentials, turns out to be answerable from the file on its own. It is the largest capability the contract permits.

  • Propose the moved block(external site)#4

    Today it reports that a rename looks like it forgot one. Writing the block you need is output on standard out, not an edit to your configuration, so the refactoring help arrives without the tool ever touching a file you own.

  • The shape of a plan, before the findings(external site)#5

    How big this change is and what kind of change it is, in a line or two, before the list starts. A reviewer decides how much attention a plan deserves before reading any of it.

  • Your own rules, evaluated over a plan(external site)#6

    Teams have rules that are theirs rather than everyone's - never destroy anything in this account, this tag is mandatory. A deterministic evaluation of rules you wrote, with no policy service and no account to sign up for.

  • A gate an agent cannot talk its way past(external site)#7

    An exit code decided by the plan rather than by argument. As more changes are proposed by agents, the useful property is a check whose answer does not move because something articulate disagreed with it.

  • Credentials Terraform did not mark sensitive(external site)#8

    Marking is best-effort, and a live credential was found in a real plan that Terraform had left unmarked. Finding them is a detection problem, not a printing one - it can say a value at this path looks like a credential without ever showing it.

  • Two plans, and what actually resolved(external site)#9

    Re-plan after a fix and the question is which findings went away, which are new, and which are exactly as they were. That is a comparison of two files, which is still two files.

  • One report across many roots(external site)#10

    Estates are split across many Terraform roots and a change often touches several. One ranked report over all of them, rather than a terminal window per directory.

  • Cost delta, from a price sheet on disk(external site)#11

    What this change does to the bill, computed against a price file you supply. Every other tool in this space asks for an API key; a price sheet is a file, and a file is inside the contract.

  • Evidence, for teams that must show their working(external site)#12

    Regulated change control needs an artefact saying what was reviewed, when, and what it said. Deterministic output is exactly what makes such an artefact worth anything.

Linting, formatting, security scanning, documentation and orchestration are all deliberately absent. Each is held by a good tool with years of accumulated rules, and aggregating them means inheriting the maintenance without earning the credibility. The aim is not to own your session - terraform already does that. It is to be the thing you hand a plan to when you need to know what it really says.

Install

go install github.com/dbhq-uk/terraken/cmd/terraken@latest

Or download a binary from the releases page (external site). The release ships terraken and tken, built from the same package, so use whichever name is free on your machine. Builds are for Linux and macOS, on amd64 and arm64.

Then run it

terraform plan -out tfplan
terraform show -json tfplan > plan.json
terraken plan.json

Or skip the file entirely. Plan JSON can hold a credential in the clear, and a file on a CI runner is one more place for it to be left behind:

terraform show -json tfplan | terraken -

Four levels, and no medium

A middle bucket is where findings go to be ignored, so there is not one.

LevelWhat reaches it
criticalA destroy or a replacement of a resource type that holds data, so destroying it loses that data. This is the only escalation in the tool, and it applies to destruction alone: updating a database in place does not lose data.
highAny other destroy, or any replacement. The resource goes away and comes back, whatever is or is not inside it.
lowAn update in place, or a resource that is being forgotten from state but left running.
infoA create, a data source read, an import and a no-op. Nothing existing is being taken away.

The source, and the tests that hold it

Every line is public and MIT licensed, and the three boundaries above are held by tests in that repository rather than by a promise on this website. It also carries the fixtures the output on this site was produced from, including a plan that exists only to be leaked from.

Terraken uses Google Analytics to count how many people read these pages. No cookie is set unless you accept, and every page works exactly the same either way.