Infrastructure Drift Detection
Detect when your cloud infrastructure diverges from your IaC definitions using Terraform plan, driftctl, and CloudQuery. Set up automated drift alerts before manual changes cause incidents.
Method 1 — Terraform Plan as Drift Detector
# Run terraform plan in CI and parse for unexpected changes
cd terraform/
# Refresh state to pull latest cloud state, then plan
terraform init -reconfigure
terraform refresh # or use -refresh=true in plan
# Generate machine-readable plan output
terraform plan -refresh=true -out=tfplan.binary -detailed-exitcode
EXIT_CODE=$?
# Exit code: 0 = no changes, 1 = error, 2 = changes detected (drift!)
terraform show -json tfplan.binary > tfplan.json
# Parse for changes using jq
jq '[.resource_changes[] | select(.change.actions | contains(["update"]) or contains(["delete"]))] |
map({resource: .address, actions: .change.actions, before: .change.before, after: .change.after})' \
tfplan.json
# Count drift by type
jq '[.resource_changes[] | .change.actions[]] |
group_by(.) | map({action: .[0], count: length})' tfplan.json
# Exit 1 in CI if drift detected
if [ $EXIT_CODE -eq 2 ]; then
echo "DRIFT DETECTED: $(jq '.resource_changes | length' tfplan.json) resources changed"
exit 1
fi Method 2 — driftctl for Unmanaged Resources
# Install driftctl curl -L https://github.com/snyk/driftctl/releases/latest/download/driftctl_linux_amd64 \ -o /usr/local/bin/driftctl && chmod +x /usr/local/bin/driftctl # Scan AWS resources against Terraform state export AWS_PROFILE=production driftctl scan \ --from tfstate+s3://my-terraform-state/prod/terraform.tfstate \ --output json://drift-report.json # View coverage and drift summary driftctl scan \ --from tfstate+s3://my-terraform-state/prod/terraform.tfstate \ --output console # Example output: # Found 14 resource(s) not covered by IaC: (unmanaged) # - aws_s3_bucket.manual-uploads-2024 # - aws_security_group.sg-0abc1234 # - aws_iam_role.manual-role # Coverage: 87.3% (127 managed / 14 unmanaged) # Scan multiple state files (multi-env) driftctl scan \ --from tfstate+s3://state-bucket/prod/terraform.tfstate \ --from tfstate+s3://state-bucket/staging/terraform.tfstate # Ignore known unmanaged resources in .driftignore cat > .driftignore <<EOF # Intentionally unmanaged — created by EKS node group aws_instance.* aws_volume_attachment.* # Managed by external team aws_iam_role.external-team-role EOF
Method 3 — CloudQuery SQL for Drift Analysis
# Install CloudQuery
curl -L https://github.com/cloudquery/cloudquery/releases/latest/download/cloudquery_linux_amd64 \
-o /usr/local/bin/cloudquery && chmod +x /usr/local/bin/cloudquery
# cloudquery.yml — sync AWS to PostgreSQL
kind: source
spec:
name: aws
registry: cloudquery
path: cloudquery/aws
version: "v23.0.0"
tables: ["aws_ec2_instances", "aws_ec2_security_groups",
"aws_iam_roles", "aws_s3_buckets"]
destinations: ["postgresql"]
---
kind: destination
spec:
name: postgresql
registry: cloudquery
path: cloudquery/postgresql
version: "v7.0.0"
spec:
connection_string: "postgresql://user:pass@localhost:5432/cloudquery"
# Run sync
cloudquery sync cloudquery.yml
# SQL: Find EC2 instances without required tags (drift from policy)
SELECT
instance_id,
instance_type,
state_name,
tags
FROM aws_ec2_instances
WHERE
state_name = 'running'
AND (tags ->> 'Environment' IS NULL
OR tags ->> 'Team' IS NULL
OR tags ->> 'Project' IS NULL);
-- Security groups with 0.0.0.0/0 inbound rules (unexpected open ports)
SELECT
sg.group_id,
sg.group_name,
perm.from_port,
perm.to_port,
perm.ip_protocol
FROM aws_ec2_security_groups sg,
jsonb_array_elements(sg.ip_permissions) AS perm,
jsonb_array_elements(perm -> 'IpRanges') AS ip_range
WHERE ip_range ->> 'CidrIp' = '0.0.0.0/0'
AND perm ->> 'IpProtocol' != '-1'
ORDER BY perm ->> 'FromPort'; CI Integration — GitHub Actions Drift Alert
# .github/workflows/drift-detection.yml
name: Infrastructure Drift Detection
on:
schedule:
- cron: '0 8 * * *' # Daily at 8 AM UTC
workflow_dispatch:
jobs:
drift-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Terraform Init
run: terraform init
working-directory: terraform/
- name: Detect Drift
id: drift
run: |
terraform plan -refresh=true -detailed-exitcode \
-out=tfplan.binary 2>&1 | tee plan_output.txt
echo "exit_code=$?" >> $GITHUB_OUTPUT
working-directory: terraform/
continue-on-error: true
- name: Alert on Drift
if: steps.drift.outputs.exit_code == '2'
uses: slackapi/[email protected]
with:
payload: |
{
"text": ":rotating_light: *Infrastructure Drift Detected*\nTerraform plan shows unexpected changes in production.\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DRIFT_WEBHOOK }} Drift Severity Classification
| Severity | Resource Type | Response |
|---|---|---|
| Critical | IAM policies, security groups, KMS keys | Immediate alert + auto-remediation |
| High | Instance types, RDS configs, network ACLs | Alert within 1 hour |
| Medium | Auto-scaling groups, load balancer rules | Daily report |
| Low | Tags, descriptions, metadata | Weekly batch review |
Frequently Asked Questions
What is infrastructure drift and why is it dangerous?▼
Infrastructure drift occurs when the actual state of your cloud infrastructure diverges from the state defined in your infrastructure-as-code (Terraform, Pulumi, etc.). It happens when someone manually changes a resource in the cloud console, when a resource is modified by an automated process outside IaC, or when resources are created outside Terraform. Drift is dangerous for several reasons: your IaC no longer accurately represents what's running (so terraform apply can destroy manual changes), security configurations may have been altered manually (opening ports, changing IAM policies), and incident response becomes harder because the 'ground truth' is unclear. Regular drift detection is an essential operational discipline.
How does driftctl differ from terraform plan for drift detection?▼
terraform plan detects drift only for resources Terraform already manages (those in the state file). driftctl detects both: managed resources that have drifted (differ from state) AND unmanaged resources — cloud resources that exist but have no corresponding Terraform state at all. Unmanaged resources are often the more important finding: shadow infrastructure, forgotten resources running up bills, or security risks. driftctl also provides a coverage score showing what percentage of your cloud infrastructure is under IaC management. terraform plan is a prerequisite for every apply; driftctl is a periodic audit tool that answers 'what's out there that we don't control?'
How should I handle drift when it's detected?▼
Drift handling depends on type. For managed resource drift (Terraform knows about it but actual state differs): determine if the manual change was intentional. If yes, update your Terraform code to match and run apply (or use terraform import if needed). If no, run terraform apply to restore the desired state. For unmanaged resources: decide to either import into Terraform (terraform import) so it's managed going forward, or use driftctl's ignore file (.driftignore) to explicitly acknowledge it. Never just delete unmanaged resources without investigation — they may be critical. Build a drift triage process: high-severity (security group changes, IAM policy changes) trigger immediate alerts; low-severity (tag changes) are batched for weekly review.
Can I detect drift in Kubernetes resources with these tools?▼
Yes, but different tools are better suited. CloudQuery can query Kubernetes resources alongside cloud resources and detect configuration drift via SQL comparisons. For Kubernetes-specific drift, consider: (1) Flux and ArgoCD both detect drift natively — they show 'OutOfSync' status when the cluster state differs from Git. (2) config-sync (Google) does the same. (3) kubectl diff can show differences between running state and your local manifests. For Helm-managed resources, helm diff (a Helm plugin) shows what a helm upgrade would change. GitOps tools solve the Kubernetes drift problem fundamentally by continuously reconciling.
Related Products
on GumroadPremium Workflow Pack
Production-grade workflows, architecture guides, and deployment scripts for teams that ship. 10 advanced CI/CD workflows (blue-green, canary, E2E, security), 5 architecture deep-dives, code review checklist, PR process guide, and 5 battle-tested Bash deployment scripts.
Buy on Gumroad →GitHub Actions CI/CD Templates Pack
12 production-ready GitHub Actions workflow templates covering Node.js, Python, Docker, multi-environment deployments, security scanning, and auto-changelog. Copy, paste, ship.
Buy on Gumroad →Developer Productivity Bundle
Stop rebuilding the same setup every project. 51 VSCode snippets, 40 Git aliases, 5 Docker Compose stacks, 5 GitHub Actions workflows, 5 README templates, and a dotfiles kit. Set up a professional dev environment in under an hour.
Buy on Gumroad →You Might Also Need
Terraform Variable Builder
Build Terraform variable declarations for variables.tf with a visual form. Set type, default value, description, sensitivity, and validation rules.
Open free tool →
Terraform Variable Validator
Validate your Terraform variables.tf online. Catches missing type constraints, undocumented variables, unmasked secrets, and type/default mismatches. Runs 100% in your browser.
Open free tool →
Terraform Variable Generator
Generate variables.tf and terraform.tfvars from a visual form. Add variables with type, description, defaults, sensitive flag, and validation blocks.
Open free tool →
More Free Tools
Related Articles
Terraform vs Pulumi 2026: Infrastructure as Code Comparison with Code Examples
Complete Terraform vs Pulumi comparison for 2026 — syntax, state management, multi-cloud support, testing, pricing, and when to choose each IaC tool.
AWS CDK vs Terraform vs Pulumi: Infrastructure as Code Comparison 2026
Complete comparison of AWS CDK, Terraform, and Pulumi for infrastructure as code in 2026 — language support, state management, multi-cloud, community, and when to use each.
Pulumi vs Terraform 2026: Which IaC Tool Should You Choose?
A deep comparison of Pulumi and Terraform for infrastructure as code in 2026. Covers syntax, state management, provider ecosystem, team adoption, and when to choose each tool.
Terraform vs Pulumi vs AWS CDK: Infrastructure as Code Comparison 2026
Complete comparison of Terraform, Pulumi, and AWS CDK for infrastructure as code in 2026. Covers language support, state management, learning curve, ecosystem, cloud support, pricing, and when to use each tool.
Terraform vs Pulumi: Infrastructure as Code Comparison 2026
In-depth comparison of Terraform and Pulumi for Infrastructure as Code in 2026. Covers HCL vs real programming languages, state management, provider ecosystem, and when to use each.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →