Terraform Drift Detection Prompt
Detect Terraform drift — scheduled plans, refresh, drift reporting, alerting, distinguishing manual changes from external mutations.
- Target user
- Platform engineers ensuring infrastructure consistency
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT
The prompt
You are a senior platform engineer who has built drift detection systems — periodic plan runs, drift reporting, alerting on critical changes.
I will provide:
- Use case (compliance, security, ops)
- Current verification
- Goal
Your job:
1. **Drift sources**:
- Manual cloud console changes
- Other tools (CloudFormation, Ansible)
- Cloud provider auto-actions (Auto Scaling, lifecycle)
- Failed previous applies
- Expected drift (e.g., scaling decisions)
2. **For detection**:
- `terraform plan -detailed-exitcode`
- Exit codes: 0=no change, 1=error, 2=drift
- Schedule periodic runs
3. **For refresh**:
- `terraform refresh` updates state from cloud
- Deprecated as command; use `terraform apply -refresh-only`
4. **For reporting**:
- Per-resource drift
- Aggregate over time
- Trend analysis
5. **For ignoring expected drift**:
- `lifecycle { ignore_changes = [...] }`
- For tags managed elsewhere
- For scaling-set sizes
6. **For acting on drift**:
- Apply Terraform to reconcile
- Or investigate and import
- Or accept and document
7. **For audit trail**:
- Drift events logged
- Per-environment
- Per-resource type
8. **For alerting**:
- Slack / PagerDuty on critical drift
- Email digest for low priority
Mark DESTRUCTIVE: auto-reconciling drift without review (wipes intentional changes), ignoring drift indefinitely (compliance issue), false-positive flood causing alerts ignored.
---
Use case: [compliance / security / ops]
Current verification: [DESCRIBE]
Goal: [DESCRIBE]
Why this prompt works
Drift erodes IaC value. This prompt walks detection.
How to use it
- Periodic plan runs.
- Alert on drift.
- Audit response.
- Lifecycle ignore_changes for expected.
Useful commands
# Drift check with exit code
terraform plan -detailed-exitcode -out=tfplan
# Exit 0: no changes (no drift)
# Exit 1: error
# Exit 2: changes (drift)
# Refresh-only (just sync state from cloud)
terraform apply -refresh-only -auto-approve
# Show drift details
terraform show -json tfplan | jq -r '.resource_changes[] | select(.change.actions[] != "no-op") | .address'
Patterns
Scheduled drift check (GitLab CI)
drift-detection-prod:
image: hashicorp/terraform:1.9
stage: drift
script:
- cd envs/prod
- terraform init
- |
set +e
terraform plan -detailed-exitcode -out=tfplan
EXIT=$?
set -e
case $EXIT in
0)
echo "No drift detected"
;;
2)
echo "DRIFT DETECTED"
terraform show -no-color tfplan | tee drift-report.txt
# Send alert
curl -X POST -H 'Content-Type: application/json' \
-d "{\"text\":\"Drift detected in prod\"}" \
"$SLACK_WEBHOOK"
exit 2
;;
*)
echo "Error checking drift"
exit $EXIT
;;
esac
artifacts:
when: always
paths: [envs/prod/drift-report.txt]
expire_in: 30 days
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
Schedule (GitLab Pipeline Schedule)
CI/CD → Schedules → New schedule:
- Description: Production drift check
- Interval: 0 6 * * * # Daily at 06:00
- Target branch: main
- Variables: DRIFT_CHECK=true
Lifecycle ignore_changes (expected drift)
resource "aws_autoscaling_group" "web" {
name = "web-asg"
min_size = 3
max_size = 10
desired_capacity = 3 # initial; ASG may scale
vpc_zone_identifier = var.subnets
lifecycle {
ignore_changes = [
desired_capacity, # scaling decisions; expected drift
tag, # tags added by cost mgmt tool
]
}
}
resource "aws_eks_cluster" "main" {
name = "prod"
lifecycle {
ignore_changes = [
kubernetes_network_config[0].service_ipv4_cidr, # may auto-set
]
}
}
Drift report aggregation
#!/bin/bash
# Parse drift JSON and produce summary
terraform plan -detailed-exitcode -out=tfplan
terraform show -json tfplan > plan.json
jq -r '
.resource_changes[] |
select(.change.actions[] != "no-op") |
{
type: .type,
address: .address,
actions: .change.actions
}
' plan.json | jq -s '
group_by(.type) |
map({type: .[0].type, count: length, addresses: map(.address)})
'
Cloud-side audit (correlate drift with manual changes)
# AWS CloudTrail query for changes outside Terraform
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=manual-user \
--start-time "$(date -d '24 hours ago' --iso-8601)" \
--max-items 50 | \
jq -r '.Events[] | "\(.EventTime) \(.Username) \(.EventName) \(.Resources[0].ResourceName // "n/a")"'
Critical resource drift alerting
# Alert on drift in security-critical resources
CRITICAL_TYPES="aws_iam_role aws_iam_policy aws_security_group aws_s3_bucket_policy"
DRIFTED=$(jq -r '.resource_changes[] |
select(.change.actions[] != "no-op") |
select(.type as $t | "'"$CRITICAL_TYPES"'" | contains($t)) |
.address
' plan.json)
if [ -n "$DRIFTED" ]; then
# PagerDuty alert
curl -X POST https://events.pagerduty.com/v2/enqueue \
-H 'Content-Type: application/json' \
-d "{
\"routing_key\": \"$PD_KEY\",
\"event_action\": \"trigger\",
\"payload\": {
\"summary\": \"Critical Terraform drift\",
\"severity\": \"critical\",
\"source\": \"terraform-drift\",
\"custom_details\": {\"drifted\": \"$DRIFTED\"}
}
}"
fi
Common findings this catches
- Tag drift from cost management → ignore_changes on tags.
- ASG scaling shows as drift → ignore_changes on desired_capacity.
- Manual IAM changes → critical; alert + reconcile.
- CloudFormation managing same resource → ownership conflict.
- Drift after upgrade → provider behavior change.
- Apply silently fixed drift without review.
- Drift report retention insufficient for audit.
When to escalate
- Critical drift in security resources → IR.
- Persistent drift = process issue.
- Multi-team ownership of resources → coordinate.
Related prompts
-
Ansible Drift Detection with Check Mode Prompt
Detect configuration drift — periodic --check runs, --diff, integration with monitoring, alerts on drift.
-
Terraform Plan Review Checklist Prompt
Review Terraform plan output — read 'destroy', 'replace', 'create', identify dangerous changes, automate review.
-
Terraform State Surgery & Import Prompt
Perform Terraform state operations — terraform state mv/rm/import, replace, large-scale imports via import block.