Terraform Guide

How to review a Terraform pull request: read the blast radius, not the diff

By the engineer who builds Datadef, from client work on data platforms · Reviewed August 21, 2026

Diff size and risk are close to uncorrelated in Terraform. A four hundred line pull request can be a rename applied by a formatter, and a twelve line one can change a for_each key and recreate a production database. Reviewing infrastructure well means reading three things the diff does not show: what gets replaced, what else consumes the thing being touched, and what the change does to the shape of the system.

7 min readFor reviewers approving infrastructure changes they did not write

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

109/20003 credits left
Try:

No account needed · Editable canvas, not a picture

What the diff hides

Replacement is the first hidden thing. Many attributes force a destroy and create rather than an update, and the HCL line that triggers it looks the same as one that does not. Only the plan says forces replacement, and only a human knows that the resource in question holds data.

Reach is the second. A one-line change inside a shared local module lands in every stack that calls it. The diff shows the module file; it does not show the four stacks downstream, and the reviewer who does not already know the repository has no way to find out from the pull request.

Shape is the third. A change that adds a resource in a different module, moves a subnet to another VNet, or introduces a dependency between two previously independent modules alters the architecture. It is exactly the kind of change worth a second opinion, and exactly the kind that reads as unremarkable in a unified diff.

A checklist that catches the expensive mistakes

Run these in order. The first three are about not losing data, the next three are about not surprising anyone, and the last is about not making the next reviewer suffer.

Point three is cheap to automate: moved blocks, available since Terraform 1.1, let a refactor rename addresses without a destroy and create, and their absence in a rename is a review finding. Point one can be automated in the pipeline too, since terraform plan -detailed-exitcode returns 0 for an empty diff, 1 for an error, and 2 when the plan contains changes, which is enough to route a plan with replacements to a human.

1  plan output: any "forces replacement" on a stateful resource?
2  for_each / count keys: did any key change, renumber, or reorder?
3  moved / removed blocks present where a refactor renamed addresses?
4  is the touched module called by more than one stack?
5  module or provider version bumps, and are they pinned?
6  backend, state key, or workspace changes?
7  does the change alter the architecture, and is the diagram going to say so?

# in CI: exit 0 = no changes, 1 = error, 2 = changes present
terraform plan -detailed-exitcode -out=tfplan

Where a diagram helps, and where it does not

The plan answers create, replace, destroy. Nothing else can, and no diagram substitutes for reading it. What the plan does not answer is what is connected to this, because plan output is a list, and reach is a graph.

A structural view answers reach. Connect the repository a second time on the feature branch, since a project is bound to a branch or a tag, and you get the shape that branch produces: modules as zones, references resolved through outputs into labelled edges, registry modules with their versions. Comparing it to the project synced from main shows what the change did to the structure, which is the part reviewers most often get wrong.

The comparison holds up because node ids come from the Terraform address rather than from any wording, with uninformative resource names such as this, main, and default dropped from the id. The same resource is the same node on both branches, so a new zone means a new module, a new arrow between zones means a coupling that did not exist yesterday, and a node that moved from one zone to another means a boundary changed.

To be clear about scope, Datadef does not post comments on pull requests and does not read plan files. What it does is keep a branch-scoped architecture view that regenerates on demand, including from a coding agent session over MCP, where repo status and refresh tools let the agent bring the diagram up to date after the change merges.

Put the check in CI, not in memory

A pipeline step that fails when the architecture changed and the docs did not is more reliable than a checklist item. See docs checks in CI.

FAQ

What should a Terraform code review check first?

The plan output for forced replacements on resources that hold data, then any change to for_each or count keys, then whether a refactor that renamed addresses included moved blocks. Those three account for most of the incidents that a review can realistically prevent.

Can I see the architecture a branch produces before merging it?

Yes, by generating a diagram from that branch. A repository connection is bound to a specific branch or tag, so connecting the feature branch as its own project gives a structural view of what the change produces, alongside the one synced from the main branch.

Does an architecture diagram replace reading terraform plan?

No. Plan is the only thing that reports creates, replacements, and destroys, and it must be read. A diagram answers a different question: what else is connected to the resources being touched, and whether the change alters the structure of the system.

How do I catch a change that would recreate a database?

Look for forces replacement in the plan on any stateful resource, and check whether the change alters a for_each key, an identifier, or an attribute the provider treats as immutable. Lifecycle prevent_destroy on those resources turns a review miss into a failed apply rather than a data loss.

Who should review infrastructure changes?

Someone who did not write the change and knows the blast radius of the module it touches. For shared modules called by several stacks, that usually means an owner from each affected stack, which is a good argument for recording module ownership somewhere the reviewer can find it.