The Infrastructure Review Problem
Every infrastructure team has the same workflow:
- Someone runs
terraform plan - The output is 200+ lines of undifferentiated text
- The reviewer scans for words like "destroy" or "replace"
- They miss one
- It ships
The problem isn't that terraform plan doesn't show you the changes — it does. The problem is that the output is not structured for human review. Creates, updates, and deletes are interleaved. Destructive changes aren't grouped. Before/after values require careful reading. And there's no way to fail the pipeline if the plan contains dangerous changes.
DeployDiff's preview command fixes this:
deploydiff preview --tf plan.json
It parses your Terraform, CloudFormation, or Pulumi plan, groups changes by action type, renders them with color-coded symbols, and optionally exits with code 1 if destructive changes are detected — making it a CI gate, not just a pretty printer.
Quick Start: Preview Your First Plan
Step 1: Install DeployDiff
# pip
pip install deploydiff
# Homebrew (macOS / Linux)
brew tap Coding-Dev-Tools/tap
brew install deploydiff
# Scoop (Windows)
scoop bucket add Coding-Dev-Tools https://github.com/Coding-Dev-Tools/scoop-bucket
scoop install deploydiff
Step 2: Generate a Terraform Plan JSON
# Generate the plan
terraform plan -out=tfplan
# Export as JSON for DeployDiff
terraform show -json tfplan > plan.json
Step 3: Preview with DeployDiff
deploydiff preview --tf plan.json
Output:
Step 4: Verbose Mode for Full Details
deploydiff preview --tf plan.json --verbose
Verbose mode shows the complete before/after state for every change — not just the diff, but the full resource configuration:
Verbose mode is for reviewers: Use standard mode for quick checks and CI output. Use --verbose when a human needs to review the full configuration diff before approving a production change.
Eight Action Types, Color-Coded
DeployDiff categorizes every infrastructure change into one of eight action types, each with a distinct symbol and color:
| Symbol | Action | Meaning | Destructive? |
|---|---|---|---|
| + | Create | New resource will be created | No |
| → | Read | Resource will be read (data source) | No |
| ~ | Update | In-place update — resource stays, config changes | No |
| - | Delete | Resource will be destroyed | Yes |
| +/- | Create-before-delete | Resource replaced — new one created first | Yes |
| -/+ | Delete-before-create | Resource replaced — old one destroyed first (downtime!) | Yes |
| ⇄ | Replace | Forced replacement — resource must be recreated | Yes |
| ← | Import | Existing resource imported into state | No |
Five of eight action types are destructive: Delete, Create-before-delete, Delete-before-create, and Replace all destroy existing resources. DeployDiff's --exit-on-destroy flag catches all of them.
CI/CD Integration: Block Destructive Changes in Pipeline
Pattern 1: Gate PRs on Destructive Changes
The most common workflow: block a pull request if the Terraform plan contains any destructive change.
# .github/workflows/infra-review.yml
name: Infrastructure Review
on:
pull_request:
paths: ['terraform/**']
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install DeployDiff
run: pip install deploydiff
- name: Terraform Init & Plan
working-directory: terraform
run: |
terraform init -input=false
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
- name: Preview changes
working-directory: terraform
run: deploydiff preview --tf plan.json --verbose
- name: Block on destructive changes
working-directory: terraform
run: deploydiff preview --tf plan.json --exit-on-destroy
How it works: The first preview call renders the full diff for the PR reviewer to read. The second preview call with --exit-on-destroy acts as the gate — if any destructive change exists, the job fails with exit code 1, blocking the PR merge.
Pattern 2: Destructive-Change Review with Auto-Label
Instead of blocking destructive changes outright, label the PR and require manual approval:
# .github/workflows/infra-label.yml
name: Infrastructure Change Label
on:
pull_request:
paths: ['terraform/**']
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install DeployDiff
run: pip install deploydiff
- name: Terraform Plan
working-directory: terraform
run: |
terraform init -input=false
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
- name: Check for destructive changes
id: check
working-directory: terraform
run: |
if deploydiff preview --tf plan.json --exit-on-destroy 2>/dev/null; then
echo "destructive=false" >> "$GITHUB_OUTPUT"
else
echo "destructive=true" >> "$GITHUB_OUTPUT"
fi
- name: Label PR
if: steps.check.outputs.destructive == 'true'
run: |
gh pr edit ${{ github.event.pull_request.number }} \
--add-label "destructive-change"
- name: Comment preview on PR
working-directory: terraform
run: |
deploydiff preview --tf plan.json --verbose | \
gh pr comment ${{ github.event.pull_request.number }} \
--body-file -
Pattern 3: Scheduled Drift Detection
Run a nightly check to detect configuration drift — resources that have changed outside of Terraform:
# .github/workflows/infra-drift.yml
name: Infrastructure Drift Detection
on:
schedule:
- cron: '0 6 * * *' # 6 AM daily
jobs:
drift-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install DeployDiff
run: pip install deploydiff
- name: Terraform Plan (refresh-only)
working-directory: terraform
run: |
terraform init -input=false
terraform plan -refresh-only -out=tfplan
terraform show -json tfplan > plan.json
- name: Check for drift
working-directory: terraform
run: |
CHANGES=$(deploydiff preview --tf plan.json 2>&1 | tee /tmp/drift.log)
if echo "$CHANGES" | grep -q "Will be updated\|Will be destroyed\|Will be replaced"; then
echo "::warning::Infrastructure drift detected!"
cat /tmp/drift.log
# Optionally: post to Slack, create a ticket, etc.
else
echo "No drift detected."
fi
Multi-Provider: Same CLI, Different Syntax
DeployDiff's preview command works with three infrastructure-as-code platforms using the same interface:
Terraform
# Generate plan
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
# Preview
deploydiff preview --tf plan.json
CloudFormation
# Generate change set
aws cloudformation create-change-set \
--stack-name my-stack \
--change-set-name my-changeset \
--template-body file://template.yaml
# Wait for completion, then export
aws cloudformation describe-change-set \
--stack-name my-stack \
--change-set-name my-changeset > changeset.json
# Preview
deploydiff preview --cfn changeset.json
Pulumi
# Generate preview
pulumi preview --json > preview.json
# Preview
deploydiff preview --pulumi preview.json
| Feature | --tf |
--cfn |
--pulumi |
|---|---|---|---|
| Grouped change summary | ✓ | ✓ | ✓ |
| Color-coded action symbols | ✓ | ✓ | ✓ |
| Verbose before/after | ✓ | ✓ | ✓ |
--exit-on-destroy |
✓ | ✓ | ✓ |
| Replacement triggers | ✓ | — | ✓ |
Three Real Review Scenarios
Scenario 1: The Instance Type Upgrade That Wasn't
A junior engineer submits a PR to upgrade an RDS instance from db.r5.large to db.r5.xlarge. They think it's an in-place update. But the Terraform resource has engine_version pinned to a version that doesn't support r5.xlarge — so Terraform marks it as a replace, not an update.
Without DeployDiff: The reviewer sees "update" in the plan output and approves it. The RDS instance is destroyed and recreated — taking the production database offline for 15 minutes.
with DeployDiff --exit-on-destroy: The CI pipeline fails. The reviewer sees the ⇄ replace symbol, the replacement trigger ("engine_version incompatible with new instance_class"), and the old resource destruction warning. The engineer fixes the engine version first.
Scenario 2: The Orphaned Resource Cleanup
After a migration, three EC2 instances are no longer referenced in the Terraform config. They'll be destroyed on the next apply. Someone adds prevent_destroy to two of them but misses the third.
With DeployDiff: The preview groups all deletions together under "Will be destroyed." The reviewer immediately sees three resources marked for deletion and can verify that each one is intentional — catching the one that shouldn't be destroyed before it's too late.
Scenario 3: Multi-Team Infrastructure Review
A platform team manages shared infrastructure. An application team submits a Terraform PR that modifies a shared security group. The platform team reviewer needs to understand the full impact quickly.
With DeployDiff verbose mode: The reviewer sees the complete before/after state of the security group — which ingress rules are added, which are removed, and which CIDR blocks are changing. They can approve or request changes without reading raw JSON.
DeployDiff Preview vs. Raw Terraform Plan Output
terraform show |
deploydiff preview |
|
|---|---|---|
| Change grouping | Interleaved by resource address | Grouped by action type (create/update/delete) |
| Action symbols | + / ~ / - | 8 distinct symbols with colors |
| Destructive flag | Manual scan for "destroy" | --exit-on-destroy automatic gate |
| Before/after details | Inline in plan output | --verbose structured diff |
| Replacement triggers | Hidden in JSON | Explicitly shown per resource |
| Multi-provider | Terraform only | Terraform + CloudFormation + Pulumi |
| CI-friendly | Parse text with grep | Exit code gating |
Combining Preview with Cost and Rollback
DeployDiff has three commands that work together for complete pre-deploy safety:
# 1. Review the changes
deploydiff preview --tf plan.json --verbose
# 2. Check the cost impact (Pro)
deploydiff cost --tf plan.json --threshold 500
# 3. Generate rollback commands
deploydiff rollback --tf plan.json
Complete pre-deploy workflow: preview shows you what changes. cost tells you what it costs. rollback gives you the escape hatch. Run all three before every production apply.
Install DeployDiff
# pip
pip install deploydiff
# Homebrew (macOS / Linux)
brew tap Coding-Dev-Tools/tap
brew install deploydiff
# Scoop (Windows)
scoop bucket add Coding-Dev-Tools https://github.com/Coding-Dev-Tools/scoop-bucket
scoop install deploydiff
Star DeployDiff on GitHub
Related Reading
- Infrastructure Rollback Commands That Actually Work — DeployDiff rollback tutorial
- Preview Infrastructure Cost Before You Deploy — DeployDiff cost estimation
- Before You Deploy: Config Drift + Cost Combo Guide — ConfigDrift + DeployDiff
- Block Deployments on Config Drift — ConfigDrift CI gating