A Workflow for Deploying Infrastructure Code with Terraform

Infrastructure deployments are not just “application deployments with different tooling.” They operate under a fundamentally different risk model.
A bad application deploy might return a 500 error. A bad infrastructure deploy can delete a production database.
That difference is why Infrastructure as Code (IaC) demands a stricter, more deliberate workflow.
In this post, I walk through a complete 7-step Terraform deployment workflow, using a real change to a webserver cluster, and highlight the critical safeguards that make infrastructure deployments safe at scale.
🔁 The 7-Step Infrastructure Deployment Workflow
1. Version Control — Enforce Discipline Early
All Terraform code lives in Git, but the key is enforcement, not storage.
For my setup:
mainbranch is protectedAt least one PR approval required
Status checks must pass No direct pushes allowed
This ensures every infrastructure change is:
Reviewed
Tested
Traceable
2. Run the Code Locally — Generate the Plan
Unlike application code, you don’t “run” Terraform.
You generate a diff against reality:
terraform workspace select dev
terraform plan -out=day21.tfplan
This step is non-negotiable.
What I reviewed:
Resources to be created: 1
Modified: 0
Destroyed: 0
Even a single “destroy” here would trigger deeper scrutiny.
👉 This is your first line of defense against unintended consequences.
3. Make the Change — Small, Isolated, Intentional
I created a feature branch and added a CloudWatch CPU alarm for my webserver cluster.
git checkout -b add-cloudwatch-alarms-day21
terraform plan -out=day21.tfplan
git commit -m "Add CPU alarm for webserver cluster"
git push origin add-cloudwatch-alarms-day21
Key principle:
Infrastructure changes should be small and reversible.
4. Submit for Review — The Plan Is the Diff
This is where infrastructure diverges sharply from application workflows.
In application code:
- Reviewers read source code
In infrastructure:
- Reviewers must understand what will happen in the real world
So I included the full terraform plan output in the PR:
📌 What this changes
Adds a CPU utilization alarm for the webserver cluster.
📌 Resources affected
Created: 1
Modified: 0
Destroyed: 0
⚠️ Blast Radius (Critical)
This is where most engineers underinvest.
For my change:
Impact is minimal (monitoring only)
No runtime disruption
But for shared infrastructure (VPCs, IAM, security groups), this section must answer:
What systems depend on this?
What breaks if apply fails halfway?
Is failure partial or total?
👉 If you can’t clearly define the blast radius, you shouldn’t deploy.
🔄 Rollback Plan (Equally Critical)
Rollback is not “we’ll figure it out.”
It must be explicit:
Revert the PR
Re-run Terraform apply with previous configuration
Restore state if necessary
For higher-risk changes, this may include:
Restoring from snapshots
Recreating deleted resources
Traffic failover strategies
👉 If rollback isn’t clear, your deployment isn’t safe.
5. Automated Tests — Gate Before Merge
My CI pipeline enforced:
terraform fmt --check
terraform validate
terraform test
Only after all checks passed could the PR be merged.
Important distinction:
These tests validate code correctness
They do NOT validate organizational policy
That’s where Sentinel comes in (more later).
6. Merge and Release — Version Infrastructure
After approval:
git tag -a "v1.4.0" -m "Add CPU alarm"
git push origin v1.4.0
Tagging matters because:
Infrastructure modules are versioned artifacts
Environments should consume explicit versions
This avoids accidental drift.
7. Deploy — Apply the Reviewed Plan
This is the most dangerous step.
So we eliminate uncertainty:
terraform apply day21.tfplan
Why this matters:
The plan was reviewed
The plan is now pinned
No new changes can sneak in
After apply:
terraform plan
Result:
No changes. Infrastructure is up-to-date.
🔐 Infrastructure-Specific Safeguards
These do not exist in application deployment workflows.
- Plan File Pinning
terraform plan -out=reviewed.tfplan
terraform apply reviewed.tfplan
Without this:
- You risk applying unreviewed changes
2. Approval Gates for Destructive Changes
If a plan shows:
terraform plan -out=reviewed.tfplan
terraform apply reviewed.tfplan
It must require:
- A separate explicit approval
This is enforced in Terraform Cloud.
- State Backup and Recovery
Terraform relies on a state file.
I configured:
S3 backend
Versioning enabled
This allows:
Rolling back to previous state versions
Recovering from failed applies
4. Blast Radius Documentation
Every PR touching shared infrastructure must include:
Dependencies
Failure scenarios
Impact scope
This is your risk model in writing.
🛡️ Sentinel — The Enforcement Layer
Validation checks syntax.
Sentinel enforces policy.
Here’s a policy I implemented:
import "tfplan/v2" as tfplan
allowed_instance_types = ["t2.micro", "t2.small", "t2.medium", "t3.micro", "t3.small"]
main = rule {
all tfplan.resource_changes as _, rc {
rc.type is not "aws_instance" or
rc.change.after.instance_type in allowed_instance_types
}
}
What this enforces:
Only approved EC2 instance types can be deployed
Any violation → deployment blocked
Why this matters:
Without Sentinel:
- Engineers can deploy anything that passes validation
With Sentinel:
- You enforce organizational rules automatically
⚖️ Infrastructure vs Application Workflows
- State Awareness
Infrastructure tracks real resources via state files.
Applications don’t.
2. Risk Profile
Infra changes can destroy systems.
App changes typically degrade behavior.
3. Plan → Review → Apply
Infrastructure requires a two-phase commit model.
Applications usually deploy directly.
📖 Key Insight
The most dangerous step is:
terraform apply
Because it executes real-world changes.
The most commonly skipped safeguard?
- Applying from a saved plan file
Skipping this introduces drift and unpredictability.
🧠 Final Thoughts
Infrastructure deployment is not just engineering. It’s risk management.
The shift is subtle but critical:
You’re not shipping code
You’re modifying live systems
And that demands:
- Explicit review Strict safeguards Enforced policies
Do this well, and Terraform becomes a force multiplier. Do it poorly, and it becomes a liability.





