DEV Community

Sudarshan Thakur
Sudarshan Thakur

Posted on

I Added Terraform Drift Detection to Every GitHub Repo in One Line

Setting up drift detection in CI always took the same 15 minutes of boilerplate. Install Python. Install tfdrift. Wire up secrets. Figure out how to upload SARIF. Handle exit codes so the job doesn't fail before the artifact uploads. Add a second scan run because SARIF and JSON can't come from the same command.

Every repo. Every time.

I got tired of it, so I published tfdrift as a GitHub Actions Marketplace action. Now it's one uses: line.

What Changed

Before:

- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
- run: pip install tfdrift
- run: tfdrift scan --format sarif --output results.sarif
  continue-on-error: true
- run: tfdrift scan --format json --output report.json --quiet
  continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif
Enter fullscreen mode Exit fullscreen mode

After:

- uses: sudarshan8417/tfdrift@v1
  with:
    sarif-output: results/tfdrift.sarif
Enter fullscreen mode Exit fullscreen mode

That's it. Python setup, install, scan, PR annotations, step summary, and SARIF output are all handled.

How It Works

The action runs tfdrift scan under the hood. Because it runs inside GitHub Actions, the existing --gha path fires automatically: every drifted resource becomes an inline PR annotation and the job summary gets a drift table.

Here's a full scheduled scan with Code Scanning upload and Slack notifications:

# .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   # for Code Scanning upload

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

      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: results/tfdrift.sarif
Enter fullscreen mode Exit fullscreen mode

Six hours later your Security tab has entries like this:

● aws_security_group.app — ingress rules modified out-of-band [critical]
● aws_instance.web — instance_type drifted from t3.medium to t3.large [high]
Enter fullscreen mode Exit fullscreen mode

Drift surfacing as native GitHub security alerts means it lives in the same place as your Dependabot findings and CodeQL results: one dashboard, one triage workflow.

The SARIF + JSON Problem (and How We Solved It)

Originally, if you wanted both a SARIF file for Code Scanning and a JSON file for artifact download, you had to run the scan twice. That meant running terraform plan twice, which is expensive for large workspaces.

v0.5.4 adds --sarif-output as a secondary output flag. One scan run, two files:

tfdrift scan \
  --format json --output drift-report.json \
  --sarif-output results/tfdrift.sarif
Enter fullscreen mode Exit fullscreen mode

The action uses this internally so you never pay the cost of two plan runs.

PR Gate: Block Merges on Critical Drift

The second use case is a PR gate. If someone merges a PR while critical drift is active, your next apply might conflict or fail unexpectedly. This blocks it:

# .github/workflows/drift-gate.yml
name: Drift Gate

on:
  pull_request:
    paths:
      - '**.tf'
      - '**.tfvars'

jobs:
  drift-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3

      - uses: sudarshan8417/tfdrift@v1
        with:
          min-severity: medium   # ignore tag noise in PRs
          fail-on: high          # block only on high+ severity
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Enter fullscreen mode Exit fullscreen mode

The job exits 1 on high or critical drift and appears as a failing required check on the PR.

Action Outputs for Conditional Steps

The action exposes two outputs, drift-count and has-drift, so you can branch on them in downstream steps:

- name: Detect drift
  id: drift
  uses: sudarshan8417/tfdrift@v1
  with:
    fail-on: critical   # only fail the job on critical

- name: Do something if drift was detected
  if: steps.drift.outputs.has-drift == 'true'
  run: |
    echo "Drifted resources: ${{ steps.drift.outputs.drift-count }}"
    # call your ticketing API here
Enter fullscreen mode Exit fullscreen mode

This lets you fail the job at one severity threshold while still triggering notifications or ticket creation at a lower one, without running a second scan.

Full Inputs Reference

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
json-output — Path for JSON report
exclude-resource — fnmatch pattern to skip noisy 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

Getting Started

Install tfdrift locally to test before wiring up CI:

pip install tfdrift
tfdrift scan --path ./infra
Enter fullscreen mode Exit fullscreen mode

Then drop the action into any repo that uses Terraform. The only hard requirement is that terraform (or tofu) is on PATH before the action runs. hashicorp/setup-terraform@v3 handles that.

Full docs and examples: github.com/sudarshan8417/tfdrift

What's Next

  • Auto-PR: when drift is detected, automatically open a GitHub PR with the AI-generated .tf remediation file
  • Jira / Linear integration: create tickets directly from drift alerts
  • tfdrift serve: HTTP API mode for embedding drift detection in internal portals and Backstage plugins

If you're using tfdrift or have feedback, open an issue or drop a star on GitHub. Both go a long way for an open-source project.

Top comments (0)