tfdrift
Continuous Terraform drift detection, reporting, and auto-remediation.
[](https://www.python.org/downloads/
tfdrift is a Python CLI tool that monitors your Terraform-managed infrastructure for drift — changes made outside of Terraform workflows (console clicks, scripts, other tools). It scans your Terraform workspaces, detects discrepancies between state and reality, generates structured reports, and optionally auto-remediates or notifies your team.
Why tfdrift? The most popular open-source drift detection tool (driftctl) has been in maintenance mode since mid-2023. Enterprise solutions like Terraform Enterprise cost $15K+/year. tfdrift fills the gap: a free, modern, actively maintained CLI that does one thing well.
Features
- Multi-workspace scanning — Recursively discovers all Terraform workspaces in a directory tree
- Structured drift reports — JSON, Markdown, CSV, or human-readable table output
- Severity classification — Categorizes drift by risk level (critical/high/medium/low) based on resource type and attribute
- Slack, Teams, OpsGenie & webhook notifications — Get alerted the moment drift is detected (Slack, Microsoft Teams, OpsGenie, PagerDuty, or any generic webhook)
- Auto-remediation — Optionally run
terraform applyto fix drift (with safety guards) - CI/CD friendly — Exit codes, JSON output, and GitHub Actions integration out of the box
- Watch mode — Continuously monitor for drift on a schedule
- Ignore rules — Filter out known/expected drift with
.tfdriftignore
Quick start
Install
pip install tfdrift
Scan for drift
# Scan current directory for all Terraform workspaces
tfdrift scan
# Scan a specific directory
tfdrift scan --path /path/to/terraform
# Output as JSON
tfdrift scan --format json
# Output as Markdown report
tfdrift scan --format markdown --output drift-report.md
# Output as CSV (one row per drifted resource — great for spreadsheets or data pipelines)
tfdrift scan --format csv --output drift-report.csv
# Exclude noisy resources (e.g. autoscaling desired_capacity)
tfdrift scan --exclude-resource "aws_autoscaling_group*"
Compare two reports (diff mode)
Save a baseline then diff against a later scan to see only net-new drift:
tfdrift scan --format json --output baseline.json
# ... deploy, changes happen ...
tfdrift scan --format json --output current.json
tfdrift diff baseline.json current.json
# In CI — exit 1 only when new drift appears
tfdrift diff baseline.json current.json --fail-on-new
Try it locally (no cloud credentials needed)
Simulate drift using Terraform's null provider and a local backend:
# 1. Create a workspace and apply initial state
mkdir -p /tmp/tfdrift-demo && cat > /tmp/tfdrift-demo/main.tf <<'EOF'
terraform {
required_providers {
null = { source = "hashicorp/null", version = "~> 3.0" }
}
backend "local" {}
}
resource "null_resource" "server" {
triggers = { instance_type = "t3.micro", region = "us-east-1" }
}
resource "null_resource" "db" {
triggers = { engine = "postgres", version = "14" }
}
EOF
cd /tmp/tfdrift-demo && terraform init && terraform apply -auto-approve
# 2. Simulate drift — change config without updating state
sed -i 's/t3.micro/t3.large/; s/us-east-1/us-west-2/; s/version = "14"/version = "15"/' main.tf
# 3. Run tfdrift
tfdrift scan --path /tmp/tfdrift-demo
Expected output:
⚠️ Drift detected: 2 resource(s) across 1/1 workspace(s)
🟡 medium: 2
📂 /tmp/tfdrift-demo (2 drifted, 0.3s)
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓
┃ Severity ┃ Resource ┃ Action ┃ Changed attributes ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩
│ MEDIUM │ null_resource.db │ replace │ id, triggers │
│ MEDIUM │ null_resource.server │ replace │ id, triggers │
└──────────┴───────────────────────┴─────────┴────────────────────┘
Watch mode (continuous monitoring)
# Check every 30 minutes, notify Slack on drift
tfdrift watch --interval 30m --slack-webhook https://hooks.slack.com/services/XXX
# Notify Microsoft Teams on drift
tfdrift watch --interval 30m --teams-webhook https://example.webhook.office.com/webhookb2/XXX
Auto-remediate
# Auto-fix drift in dev (requires --confirm for safety)
tfdrift scan --auto-fix --confirm --env dev
# Dry run — show what would be fixed
tfdrift scan --auto-fix --dry-run
Configuration
Create a .tfdrift.yml in your project root:
# .tfdrift.yml
scan:
paths:
- ./infrastructure
- ./modules
exclude:
- "**/test/**"
- "**/.terraform/**"
# Auto-detect .tfvars files in each workspace (default: true)
auto_detect_var_files: true
# Or specify explicit var files
var_files:
- envs/dev.tfvars
# Or pass variables directly
vars:
environment: production
region: us-east-1
severity:
critical:
- aws_security_group.*.ingress
- aws_iam_policy.*.policy
- aws_s3_bucket.*.acl
high:
- aws_instance.*.instance_type
- aws_rds_instance.*.engine_version
notifications:
slack:
webhook_url: ${SLACK_WEBHOOK_URL}
channel: "#infra-alerts"
min_severity: high
teams:
webhook_url: ${TEAMS_WEBHOOK_URL}
min_severity: high
opsgenie:
api_key: ${OPSGENIE_API_KEY}
min_severity: high
region: us # or eu
webhook:
url: ${WEBHOOK_URL}
method: POST
remediation:
auto_fix: false
allowed_environments:
- dev
- staging
require_approval: true
max_changes: 5 # safety limit
ignore:
# Ignore expected drift
- resource: aws_autoscaling_group.*
attribute: desired_capacity
- resource: aws_ecs_service.*
attribute: desired_count
Ignore rules
Create a .tfdriftignore file to skip known drift:
# Autoscaling changes are expected
aws_autoscaling_group.*.desired_capacity
aws_ecs_service.*.desired_count
# Tags managed by external system
*.tags.LastModified
*.tags.UpdatedBy
CI/CD Integration
GitHub Actions
The fastest way to add drift detection: one uses: line, no Python setup required.
# .github/workflows/drift-check.yml
name: Terraform Drift Check
on:
schedule:
- cron: '0 */6 * * *'
pull_request:
workflow_dispatch:
jobs:
drift:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # only needed for sarif-output
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Detect drift
id: drift
uses: sudarshan8417/tfdrift@v1
with:
path: .
min-severity: low
fail-on: high
sarif-output: results/tfdrift.sarif
json-output: results/drift-report.json
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Upload to GitHub Code Scanning
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results/tfdrift.sarif
- run: |
echo "Drifted resources: ${{ steps.drift.outputs.drift-count }}"
Action inputs
| Input | Default | Description |
|---|---|---|
path |
. |
Root directory to scan |
min-severity |
info |
Minimum severity to report |
fail-on |
(any drift) | Severity threshold to exit 1 |
sarif-output |
— | Path for SARIF file (Code Scanning) |
json-output |
— | Path for JSON report |
exclude-resource |
— | fnmatch pattern to skip resources |
binary |
terraform |
Path to terraform or tofu |
workers |
4 |
Parallel scan workers |
tfdrift-version |
latest | Pin a specific PyPI version |
slack-webhook |
— | Slack Incoming Webhook URL |
opsgenie-key |
— | OpsGenie API key |
config |
— | Path to .tfdrift.yml |
Action outputs
| Output | Description |
|---|---|
drift-count |
Total drifted resources detected |
has-drift |
"true" / "false" |
See examples/ for a PR gate workflow and a scheduled scan with Code Scanning.
GitLab CI
drift-check:
image: python:3.11
before_script:
- pip install tfdrift
- apt-get update && apt-get install -y terraform
script:
- tfdrift scan --format json --output drift-report.json --min-severity low
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
artifacts:
paths:
- drift-report.json
when: on_failure
Exit codes
| Code | Meaning |
|---|---|
| 0 | No drift detected |
| 1 | Drift detected |
| 2 | Error during scan |
| 3 | Drift detected and auto-remediated |
Architecture
tfdrift/
├── commands/ # CLI command handlers (scan, watch, init)
├── detectors/ # Drift detection engine (terraform plan parser)
├── reporters/ # Output formatters (JSON, Markdown, table, Slack)
├── remediators/ # Auto-fix logic with safety guards
├── config.py # Configuration loader (.tfdrift.yml)
├── models.py # Data models (DriftResult, Resource, etc.)
├── severity.py # Severity classification engine
└── cli.py # CLI entry point (Click)
Comparison with alternatives
| Feature | tfdrift | driftctl (archived) | terraform plan | Terraform Enterprise |
|---|---|---|---|---|
| Active maintenance | ✅ | ❌ (since 2023) | ✅ | ✅ |
| Multi-workspace scan | ✅ | ❌ | ❌ | ✅ |
| Severity classification | ✅ | ❌ | ❌ | ❌ |
| Auto-remediation | ✅ | ❌ | ❌ | ✅ |
| Slack/webhook alerts | ✅ | ✅ | ❌ | ✅ |
| Watch mode | ✅ | ❌ | ❌ | ✅ |
| Ignore rules | ✅ | ✅ | ❌ | ❌ |
| JSON / Markdown / CSV output | ✅ | ❌ | ❌ | ✅ |
| Cost | Free | Free | Free | $15K+/yr |
| Language | Python | Go | Go (HCL) | Proprietary |
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
# Development setup
git clone https://github.com/sudarshan8417/tfdrift.git
cd tfdrift
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
pytest
License
Apache License 2.0 — see LICENSE for details.
Acknowledgments
Inspired by driftctl and the Terraform community's need for maintained, open-source drift detection tooling.
Metadata
Release files for tfdrift 0.5.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tfdrift-0.5.4.tar.gz | 70.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tfdrift-0.5.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 128.4 kB
Release files / tfdrift-0.5.4.tar.gz
| Download URL | tfdrift-0.5.4.tar.gz |
|---|---|
| Size | 70.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aeb1ec5e0acb25d799627dbb1246562591bad5d936de18aa667f00c1f30742d6
|
|
BLAKE2b-256 checksum How to use checksums |
3d934553721e20486a228d2db36665e9c9a5b4b0ad0b3f030728d3a80c967f4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / tfdrift-0.5.4-py3-none-any.whl
| Download URL | tfdrift-0.5.4-py3-none-any.whl |
|---|---|
| Size | 58.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
47144598b4bcd8128df8f8e044fd111f002c47270a0b6d6075f573fa12908643
|
|
BLAKE2b-256 checksum How to use checksums |
6f05844ffdb75b886e02f172e7d6b9ae66e4e005fed66172f843cb580e3fc152
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|