Skip to main content

tfdrift

Continuous Terraform drift detection, reporting, and auto-remediation.

[Python 3.9+](https://www.python.org/downloads/ PyPI version License: Apache-2.0 CI

tfdrift demo


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 apply to 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)

Source distribution for tfdrift 0.5.4
File Size Uploaded
tfdrift-0.5.4.tar.gz 70.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tfdrift 0.5.4
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.5.4 This release

2 release files

0.5.3

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page