Preview Infrastructure Cost and Blast Radius Before You Deploy
You know the feeling: you run terraform plan, scan the output, and think "looks fine." Then you deploy and discover it's replacing an RDS instance (downtime), adding $400/mo in unplanned costs, or — worst case — deleting a production database because the state file was stale.
DeployDiff is a CLI tool that sits between your infrastructure plan and your apply step. It reads Terraform plan JSON, CloudFormation change sets, and Pulumi previews — then produces three things every deployment needs:
- Resource-level diffs — exactly what's being created, modified, replaced, or destroyed
- Cost impact estimates — monthly cost before vs. after your change
- Pre-generated rollback commands — so recovery isn't panic-mode
And it all runs in CI, so you can gate deployments on cost thresholds or destructive changes.
1. Install DeployDiff
Or from GitHub:
Verify it's installed:
You'll see three subcommands: preview, cost, and rollback.
2. Generate a Terraform Plan
First, create a sample Terraform configuration so we have something to diff. Create a file called main.tf:
provider "aws" {
region = "us-east-1"
}
resource "aws_db_instance" "main" {
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.micro"
allocated_storage = 20
db_name = "myapp"
username = "admin"
password = "pleasechange" # DeployDiff wouldn't let this ship
backup_retention_period = 7
skip_final_snapshot = false
}
resource "aws_s3_bucket" "logs" {
bucket = "myapp-logs-2026"
}
resource "aws_iam_role" "app_role" {
name = "myapp-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
}]
})
}
Generate a plan and save it as JSON:
This plan.json is what DeployDiff reads.
3. Preview Resource Changes
DeployDiff outputs a structured summary:
╔══════════════════════════════════════════════════════════╗
║ DeployDiff Preview ║
╠══════════════════════════════════════════════════════════╣
║ Summary ║
║ Resources: 3 ║
║ Creates: 3 ║
║ Updates: 0 ║
║ Deletes: 0 ║
║ Replaces: 0 ║
╠══════════════════════════════════════════════════════════╣
║ Resources ║
║ ║
║ ➕ aws_db_instance.main ║
║ ├ engine: postgres ║
║ ├ instance_class: db.t3.micro ║
║ ├ storage: 20 GB ║
║ └ backup_days: 7 ║
║ ║
║ ➕ aws_s3_bucket.logs ║
║ └ bucket: myapp-logs-2026 ║
║ ║
║ ➕ aws_iam_role.app_role ║
║ └ name: myapp-role ║
╚══════════════════════════════════════════════════════════╝
This is much more readable than a raw Terraform plan JSON — especially when you have dozens of resources. The summary row tells you instantly: 3 creates, 0 destroys, 0 replaces.
4. Estimate Cost Impact
The cost command reads pricing metadata from your plan and estimates the monthly bill:
╔══════════════════════════════════════════════════════════╗
║ DeployDiff Cost Estimate ║
╠══════════════════════════════════════════════════════════╣
║ Resource Current New Δ ║
║ ──────────────────────────────────────────────────────── ║
║ aws_db_instance.main $0.00 $17.52 +$17.52 ║
║ aws_s3_bucket.logs $0.00 $0.00 $0.00 ║
║ aws_iam_role.app_role $0.00 $0.00 $0.00 ║
║ ──────────────────────────────────────────────────────── ║
║ Total $0.00 $17.52 +$17.52 ║
╚══════════════════════════════════════════════════════════╝
Now suppose a teammate changes the DB instance class and generates a new plan:
╔══════════════════════════════════════════════════════════╗
║ DeployDiff Cost Estimate ║
╠══════════════════════════════════════════════════════════╣
║ Resource Current New Δ ║
║ ──────────────────────────────────────────────────────── ║
║ aws_db_instance.main $17.52 $267.43 +$249.91║
╚══════════════════════════════════════════════════════════╝
⚠ Cost increase: $249.91/mo — above default threshold
5. Generate Rollback Commands
When things go wrong, every second counts. Don't google Terraform rollback syntax under incident pressure:
╔══════════════════════════════════════════════════════════╗
║ DeployDiff Rollback ║
╠══════════════════════════════════════════════════════════╣
║ To roll back the last apply: ║
║ ║
║ 1. terraform plan -destroy -out=rollback.tfplan ║
║ 2. terraform apply rollback.tfplan ║
║ ║
║ Resources that would be removed: ║
║ aws_db_instance.main ║
║ aws_s3_bucket.logs ║
║ aws_iam_role.app_role ║
╚══════════════════════════════════════════════════════════╝
These are ready to copy-paste — no syntax guessing under fire.
6. CI/CD Pipeline — Gate on Cost and Destructive Changes
Here's the real value: gating deployments automatically. Add this GitHub Actions workflow to your IaC repo:
# .github/workflows/deploy-preview.yml
name: Deploy Preview
on:
pull_request:
paths:
- 'terraform/**'
- '*.tf'
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Plan
run: |
terraform init
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
- name: Install DeployDiff
run: pip install deploydiff
- name: Preview infrastructure changes
run: deploydiff preview --tf plan.json
- name: Check cost impact
id: cost
run: |
deploydiff cost --tf plan.json --threshold 200
echo "cost_exceeded=$?" >> $GITHUB_OUTPUT
- name: Check for destructive changes
id: destructive
run: |
deploydiff preview --tf plan.json --exit-on-destroy
echo "has_destroys=$?" >> $GITHUB_OUTPUT
- name: Comment PR with report
if: always()
uses: actions/github-script@v7
with:
script: |
const costExceeded = '${{ steps.cost.outputs.cost_exceeded }}' === '1';
const hasDestroys = '${{ steps.destructive.outputs.has_destroys }}' === '1';
let body = '## DeployDiff Preview\n\n';
if (costExceeded) body += '⚠️ **Cost exceeds $200/mo threshold**\n';
if (hasDestroys) body += '🚨 **Destructive changes detected (resources will be replaced/destroyed)**\n';
body += '\nReview the DeployDiff output above before approving.';
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body
});
Now every PR that touches infrastructure gets:
- ✅ A resource-level diff summary
- ✅ Cost impact flagged if above threshold
- ✅ Alert if any resources are being destroyed or replaced
- ✅ A PR comment with any warnings
--cfn changeset.json) and Pulumi previews (--pulumi preview.json) using the same commands.
Command Reference
| Command | Description |
|---|---|
deploydiff preview --tf plan.json | Show resource-level diff summary |
deploydiff preview --tf plan.json --exit-on-destroy | Exit non-zero if any resources are destroyed/replaced |
deploydiff cost --tf plan.json | Estimate monthly cost impact |
deploydiff cost --tf plan.json --threshold 500 | Exit non-zero if cost increase exceeds $500 |
deploydiff rollback --tf plan.json | Generate rollback commands for the last apply |
Supported Providers
| Flag | Provider | Input Format |
|---|---|---|
--tf | Terraform | terraform show -json plan.tfplan output |
--cfn | CloudFormation | Change set JSON (via AWS CLI) |
--pulumi | Pulumi | pulumi preview --show-readings --json output |
Real-World Scenario: The $2,400 Cost Regression
A fintech team was reviewing a PR that bumped their RDS instance class "for better performance." The change would have cost an extra $2,400/month across their multi-region setup. DeployDiff caught it in the PR — the reviewer saw the cost column and said "let's benchmark first." They optimized queries instead and never needed the upgrade. Without DeployDiff, the change would have sailed through code review (it was a one-line diff) and shown up on next month's AWS bill.
Next Steps
DeployDiff is one of 10 tools in the Revenue Holdings suite. Each one runs in CI to catch problems before they hit production:
- ConfigDrift — Detect environment drift before it breaks production (tutorial)
- API Contract Guardian — Catch breaking API schema changes in PRs
- json2sql — Convert JSON to SQL INSERT statements (tutorial)
- DeadCode — Remove unused exports, dead routes, and orphaned CSS
Get Notified About New Tutorials
DevOps guides, release notes, and product updates — no spam, unsubscribe anytime.