Seven Steps. One Plan File. Zero Surprises.
Day 21 of the 30-Day Terraform Challenge — and today I learned that deploying infrastructure code safely requires discipline that application deployments don't need.
Yesterday I mapped the seven-step application workflow. Today I applied it to infrastructure — and discovered where the two workflows diverge and why those differences matter.
The Seven-Step Infrastructure Workflow
| Step | Action |
|---|---|
| 1 | Version control (Git) |
| 2 | Run locally (terraform plan -out) |
| 3 | Make code changes (feature branch) |
| 4 | Submit for review (PR with plan output) |
| 5 | Run automated tests (CI) |
| 6 | Merge and release (tag) |
| 7 | Deploy (apply saved plan file) |
Step 1: Version Control
Every infrastructure change starts in Git. Not in the AWS Console.
git init
git add .
git commit -m "Day 21: Initial web server"
git push origin main
Why this matters: Every change has an author, a timestamp, and a reason. No more mystery infrastructure.
Step 2: Run Locally — The Infrastructure Difference
For application code, running locally means executing the binary. For infrastructure, it means running terraform plan.
terraform plan -out=day21.tfplan
The critical difference: You're not running the code. You're generating a diff against your state file. The plan shows exactly what will change in production.
Step 3: Make Code Changes
Create a feature branch and make your change. I added a CloudWatch alarm:
git checkout -b add-cloudwatch-alarm
# Edit main.tf to add the alarm resource
git add main.tf
git commit -m "Add CloudWatch CPU alarm"
git push origin add-cloudwatch-alarm
Step 4: Submit for Review — The Infrastructure Difference
For application code, you review the code diff. For infrastructure, you review the plan output.
My PR description:
## What this changes
Adds a CloudWatch metric alarm for CPU utilization
## Terraform plan output
Plan: 1 to add, 0 to change, 0 to destroy.
## Resources affected
- Created: 1 (CloudWatch alarm)
## Blast radius
Low. Only adds monitoring. No impact on existing resources.
## Rollback plan
Remove the alarm resource and re-apply.
Why this matters: The reviewer sees exactly what will change in production without running Terraform themselves.
Step 5: Run Automated Tests
GitHub Actions runs terraform validate, terraform fmt --check, and unit tests automatically on every PR.
Why this matters: Catch syntax and formatting errors before a human ever reviews the code.
Step 6: Merge and Release
After approval, merge and tag:
git checkout main
git pull origin main
git tag -a "v1.1.0" -m "Add CloudWatch CPU alarm"
git push origin v1.1.0
Why this matters: Tags create rollback points. Every release is versioned.
Step 7: Deploy — The Infrastructure Difference
For application code, you deploy from CI. For infrastructure, you apply the saved plan file.
terraform apply day21.tfplan
The critical difference: Using the saved plan guarantees that exactly what was reviewed is what gets applied. No surprises. No drift between plan and apply.
What I Deployed
A web server with a CloudWatch alarm:
| Component | Status |
|---|---|
| EC2 instance | Running, serving HTTP |
| Security group | Allow port 80 |
| CloudWatch alarm | Monitoring CPU > 80% |
Verification:
$ curl -s http://56.228.18.125
<h1>Day 21: Infrastructure Deployment Workflow</h1>
$ aws cloudwatch describe-alarms --alarm-names day21-high-cpu
{
"AlarmName": "day21-high-cpu",
"StateValue": "OK",
"Threshold": 80.0
}
Infrastructure-Specific Safeguards
These have no equivalent in application code deployment:
1. Plan File Pinning
Always apply from a saved plan, never from a fresh plan.
# Correct — apply exactly what was reviewed
terraform plan -out=reviewed.tfplan
terraform apply reviewed.tfplan
# Risky — the plan may differ
terraform apply
2. Blast Radius Documentation
Every PR must document what breaks if the apply fails.
3. State Backup
S3 bucket versioning must be enabled. Know how to restore a previous state.
4. Approval Gates for Destructive Changes
Any plan showing resource destruction requires explicit approval beyond PR review.
Infrastructure vs Application: Key Differences
| Difference | Why It Exists |
|---|---|
| Plan files, not binaries | Infrastructure changes affect real resources; plan must be reviewed before apply |
| Blast radius | A bad infrastructure deploy can destroy production data |
| State management | Terraform tracks real resources; state corruption breaks everything |
The Most Dangerous Step
The gap between terraform plan and terraform apply is where things go wrong. If infrastructure changes between plan and apply, the plan becomes invalid.
The safeguard: Save the plan file and apply it directly. Never run terraform apply without a plan file.
# Safe
terraform plan -out=day21.tfplan
terraform apply day21.tfplan
# Risky
terraform apply
What I Learned
Plan output is the most important review artifact. Include it in every PR.
Blast radius matters. A bad infrastructure deploy can break more than a bad code deploy.
Plan file pinning is non-negotiable. Without it, you're applying something that might not match what was reviewed.
State is the source of truth. Protect it with versioning and backups.
The Bottom Line
Deploying infrastructure code requires the same seven steps as application code — plus infrastructure-specific safeguards.
| Before | After |
|---|---|
| Console clicks | Pull requests with plan output |
| "Who changed this?" | Git blame |
| Hope it works | Plan file proves it |
| Can't roll back | Tags and state versioning |
Seven steps. Plan files. Blast radius documentation. State backups.
Infrastructure deployment shouldn't be risky. It should be reviewed, tested, and applied exactly as planned.
P.S. The moment I applied a saved plan file and saw exactly what was reviewed happen in production, I understood why teams adopt this workflow. It's not slower. It's safer. 🔧
Top comments (0)