Skip to main content

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

Project description

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 & webhook notifications — Get alerted the moment drift is detected
  • 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

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

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
  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

# .github/workflows/drift-check.yml
name: Terraform Drift Check
on:
  schedule:
    - cron: '0 */6 * * *'  # Every 6 hours
  workflow_dispatch:

jobs:
  drift-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install tfdrift
      - run: tfdrift scan --format json --output drift-report.json --min-severity low
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: drift-report
          path: drift-report.json

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tfdrift-0.2.6.tar.gz (42.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tfdrift-0.2.6-py3-none-any.whl (39.7 kB view details)

Uploaded Python 3

File details

Details for the file tfdrift-0.2.6.tar.gz.

File metadata

  • Download URL: tfdrift-0.2.6.tar.gz
  • Upload date:
  • Size: 42.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for tfdrift-0.2.6.tar.gz
Algorithm Hash digest
SHA256 709d129d42eb9e14bd1f649260133326bbc969d1aad6f9cf347152c25dd05260
MD5 f5acc2577eec962a0a57ac2957908633
BLAKE2b-256 250ddb03724a8577d72c78eeb976f532a007d21553e8e1a209756e230dfc2a1d

See more details on using hashes here.

File details

Details for the file tfdrift-0.2.6-py3-none-any.whl.

File metadata

  • Download URL: tfdrift-0.2.6-py3-none-any.whl
  • Upload date:
  • Size: 39.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for tfdrift-0.2.6-py3-none-any.whl
Algorithm Hash digest
SHA256 39a52e36f4c77a5da7e3aa4dd1f915b34dd04205c4e161a49e634395f30674a9
MD5 03bb1c139fcf574d0049e7badf1aa23d
BLAKE2b-256 6a35e6b454c81fbeea4a3707d4b96745cf6ac465b593f1d077272375a3266a21

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page