Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLIX · Infrastructure CIInfrastructure CI

Plan and review — terraform plan as the review artefact; the comment-on-PR pattern

Intermediate⏱ ~22 mingit

What you'll learn

  • Run terraform plan -out=tfplan and explain why the plan is saved as a binary
  • Convert a plan to JSON with terraform show -json tfplan and explain what JSON adds
  • Implement the comment-on-PR pattern for surfacing plan output on pull requests
  • Recognise why re-planning between review and apply breaks the review contract

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

The plan-and-review stage is the sixth gate in the pipeline. It is the gate that humans operate; everything before it is mechanical, everything after it depends on the artefact this stage produces. The artefact is the plan: the exact set of changes Terraform would make if apply were run right now. The plan is what a reviewer approves, and the plan is what the apply stage executes. The two must be the same plan. A pipeline that re-plans between review and apply is a pipeline that has lost the meaning of review.

Plan as a binary

terraform plan produces two outputs by default: a human-readable diff on stdout and a plan file on disk if the -out flag is given. The plan file is a binary; it is not a text file, it is not a diff, and it cannot be regenerated from the diff. The binary contains the exact state of every resource Terraform intends to change, plus enough metadata for terraform apply to recognise and apply that exact plan without re-evaluating.

terraform plan -out=tfplan

The -out=tfplan argument tells Terraform to save the plan to a file called tfplan. The binary is the artefact that the apply stage will use:

terraform apply tfplan

Without -out, the apply stage would have to re-plan to know what to do. Re-planning can produce a different plan: a resource may have drifted, a provider may have returned different values, or a variable sourced from a remote backend may have changed. A re-plan between review and apply is a new plan that has not been reviewed. The review contract is broken.

flowchart LR
    A["plan -out=tfplan"] --> B["Binary plan file"]
    B --> C["Reviewer reads diff"]
    B --> D["Apply stage reads binary"]
    C --> E["Approve"]
    E --> D

Plan as JSON

The binary plan is the artefact for terraform apply, but it is not human-readable. For review surfaces — pull request comments, dashboards, audit logs — the plan needs to be in a structured format. Terraform provides this with terraform show:

terraform show -json tfplan > tfplan.json

The JSON output contains every resource the plan would create, update, or destroy, with full attribute values before and after, plus the action Terraform would take and the reason. The JSON is what the comment-on-PR pattern posts to the pull request, what the audit pipeline archives, and what downstream tooling diffs against previous plans.

The canonical fields reviewers read:

  • resource_changes[].change.actions — the list of actions Terraform will take: ["create"], ["update"], ["delete"], or combinations like ["delete", "create"] for replacement.
  • resource_changes[].change.before and resource_changes[].change.after — the resource state before and after the apply.
  • resource_changes[].change.replacing — true when a create-after-delete sequence will occur.

The comment-on-PR pattern

The review surface for a Terraform pull request is the pull request itself. The plan must appear on the pull request as a comment, updated on every push, so the reviewer can read the proposed changes without leaving the review tool. The comment-on-PR pattern is the standard implementation:

plan:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: hashicorp/setup-terraform@v3
    - run: terraform init
    - run: terraform plan -out=tfplan
    - run: terraform show -json tfplan > tfplan.json
    - uses: actions/upload-artifact@v4
      with:
        name: tfplan
        path: tfplan
    - uses: actions/upload-artifact@v4
      with:
        name: tfplan-json
        path: tfplan.json
    - name: Comment plan on PR
      uses: actions/github-script@v7
      with:
        script: |
          const fs = require('fs');
          const plan = fs.readFileSync('tfplan.json', 'utf8');
          // ... render and post comment

The comment-on-PR pattern has four moving parts that must agree:

  • The plan binary is uploaded as an artefact. The apply job downloads that artefact and runs terraform apply against it.
  • The plan JSON is uploaded as a separate artefact.
  • The pull request comment is updated with a rendered version of the plan: resource actions, count of changes, and a link to the full JSON artefact.
  • The apply job requires that the comment has been approved — either through a GitHub Actions environment approval, a /apply slash command from a reviewer, or a required status check tied to the comment.

The discipline is that the binary plan and the rendered comment are produced by the same job. A reviewer reading the comment is reading the same bytes the apply stage will execute.

Why re-planning breaks the contract

A re-plan between review and apply is the failure mode the entire plan-and-review stage exists to prevent. The sequence that breaks the contract is:

  1. Reviewer reads the comment, sees no resource replacements, approves.
  2. An unrelated change touches a data source the plan used. The variable or remote data changes between review and apply.
  3. The apply stage re-plans. The re-plan produces a different plan, including a resource replacement the reviewer did not see.
  4. The apply executes the un-reviewed plan.

The fix is to make re-planning impossible:

  • The apply job downloads the exact plan binary that was produced by the plan job. No re-plan command runs.
  • The apply job runs terraform apply tfplan with the binary as the argument, not terraform apply -auto-approve with no argument.
  • If the plan binary does not exist or does not match the committed SHA, the apply job fails before any change is made.

Production discipline

Three rules govern the plan-and-review stage:

  1. The plan that is reviewed is the plan that is applied. A pipeline that re-plans between review and apply has lost the meaning of review.
  2. Sensitive values are redacted before the plan leaves the runner. The JSON plan is treated as sensitive output regardless of sensitive annotations in the configuration.
  3. The plan binary is the artefact of record. The plan binary is archived alongside the apply job’s logs; six months later, when an audit asks what was applied, the binary answers.

Cross-course references

  • Terraform for Production Sysadmins — Part XXVI (PlanReview) covers the plan-as-artefact pattern in depth, including redaction patterns for sensitive variables.
  • Kubernetes for Production Sysadmins — Part XXXV (GitOps) covers the analogous pattern in GitOps controllers, where the diff between desired and actual state replaces the plan.
  • Pipeline Internals — Part XLIV (Artifacts) covers the artefact-handling primitives that the plan binary and JSON rely on.

Quiz

Knowledge check · 4 questions

  1. Q1. A pipeline runs `terraform plan` on every pull request, posts the diff as a comment, then runs `terraform apply -auto-approve` on merge. What contract has been broken?

  2. Q2. `terraform show -json tfplan` redacts variables marked `sensitive = true` from the output JSON.

  3. Q3. Give the command that produces a binary plan file and the command that converts that file to JSON, naming the flag each uses.

  4. Q4. Diagnose an audit failure where the bytes that were reviewed cannot be matched to the bytes that were applied, and propose the corrected pipeline.

    Six months after a Terraform apply, an auditor asks: which exact plan bytes were approved, and which exact bytes were applied? The team can produce the review comment with the rendered plan, but the apply job ran `terraform apply -auto-approve` and did not archive the plan binary. The audit cannot prove that the applied plan matches the reviewed plan.

Passing score: 75%. Answers are checked in this browser.