Stage 4 · Provision
Advanced Terraform Patterns
Drift Detection & Remediation
Automated drift detection, prevention, and self-healing — keeping infrastructure consistent with code.
What Is Drift?
Drift occurs when the actual state of infrastructure diverges from the state Terraform expects. Someone changes a security group rule in the console, a tag gets updated externally, or a resource is modified outside of Terraform. The state file no longer matches reality.
Drift is dangerous because Terraform assumes the state file is accurate. On the next apply, Terraform may revert legitimate changes or miss critical updates. Undetected drift can lead to security vulnerabilities, outages, and configuration inconsistencies.
Common Causes
- Manual changes via cloud console — the most common source of drift.
- External automation tools — scripts or other IaC tools modifying the same resources.
- Auto-scaling or auto-healing — cloud-native features that modify instances.
- Tagging policies — organizational tags applied by AWS Organization or Azure Policy.
- Provider-side changes — cloud providers updating defaults or deprecating attributes.
Detecting Drift
Terraform does not continuously check for drift. You must actively run terraform plan to detect it. Automate this in a CI pipeline or use a scheduled job.
$ terraform plan -detailed-exitcode -no-color > plan.out
EXIT_CODE=$?
case $EXIT_CODE in
0) echo "No changes — infrastructure is in sync" ;;
1) echo "Error — plan failed" ;;
2) echo "Drift detected — plan shows changes" ;;
esacThe -detailed-exitcode flag makes the exit code meaningful: 0 means no changes, 1 means error, 2 means changes exist. This is perfect for CI pipelines that need to act on the plan result.
name: Drift Detection
on:
schedule:
- cron: '0 8 * * 1' # Every Monday at 8am UTC
jobs:
detect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Check for drift
run: |
terraform init -backend=true
terraform plan -detailed-exitcode -no-color | tee plan.out
if [ $? -eq 2 ]; then
echo "Drift detected — creating issue"
fiThe cron trigger runs drift detection weekly. If drift is detected, the workflow can create a GitHub issue, send a Slack notification, or trigger a remediation pipeline.
Refresh-only Plans
A refresh-only plan updates the state file to match reality without proposing any changes. This is useful when you want to acknowledge drift without reverting it — for example, when an external change is legitimate.
$ terraform plan -refresh-only
You can apply this plan to save the new values in terraform.tfstate,
without changing any real infrastructure.
# aws_instance.web has changed
~ tags = {
- "Name" = "web-server"
+ "Name" = "web-server-prod"
}
Plan: 0 to add, 0 to change, 0 to destroy.The refresh-only plan shows what Terraform will update in the state file. Running terraform apply with this plan updates the state to match reality without modifying any resources.
Remediation Strategies
When drift is detected, you have three options: revert the change, import the change into Terraform, or document the manual process. The right choice depends on whether the change was intentional.
$ terraform plan # Shows what will change to revert drift
$ terraform apply # Reverts the drift
# Or import the change if it was intentional
$ terraform import aws_instance.web i-0123456789abcdef0terraform apply after a drift-detecting plan will revert the drifted resources to match the state file. Use terraform import to bring external changes under Terraform management instead.
Preventing Drift
- Lock down IAM permissions — restrict who can modify infrastructure outside Terraform.
- Use SCPs or Azure Policies — block manual changes at the organization level.
- Document the break-glass process — allow manual changes only with approval and follow-up import.
- Monitor cloud trail logs — alert on console API calls that modify managed resources.
- Use ignore_changes for legitimate exceptions — tags managed by external systems.
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
lifecycle {
ignore_changes = [tags["CostCenter"]]
}
}The ignore_changes block tells Terraform to skip certain attributes during diff. This prevents Terraform from reverting tag changes made by external cost allocation tools while still tracking other changes.
Some drift is intentional — auto-scaling groups changing instance counts, cloud providers updating AMIs, or security teams patching configurations. The goal is to detect all drift, decide which is intentional, and bring the rest back in sync.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.