A GitHub Action for running infrastructure drift detection with DriftHound. This action automates drift checks for Terraform, OpenTofu, and Terragrunt projects in CI/CD pipelines, with first-class support for monorepos.
- π Multi-tool support - Terraform, OpenTofu, and Terragrunt
- ποΈ Monorepo friendly - Define multiple drift scopes in one configuration
- β‘ Parallel execution - Run checks in parallel using GitHub Actions matrix strategy
- π― Selective checks - Run specific scopes or filter by criteria
- π Environment-based auth - Handle different credentials per environment (prod/staging/dev)
- π Rich reporting - GitHub Actions summary with detailed drift information
- π Extensible outputs - Integrate with GitHub Issues, deployment gates, metrics systems, and more
- π§ Automatic tool installation - No need to pre-install Terraform/OpenTofu/Terragrunt
- π Secure - Uses GitHub Actions secrets for sensitive data
Create a drifthound.yaml file in your repository root:
# Set your IaC tool
default_tool: terraform
# Optional: Specify tool version
tool_versions:
terraform: "1.6.0"
# Define drift detection scopes
scopes:
- name: "core-infrastructure-prod"
project: "my-app"
environment: "production"
directory: "./terraform/core"
slack_channel: "#infra-alerts"
- name: "networking-prod"
project: "my-app"
environment: "production"
directory: "./terraform/networking"Create .github/workflows/drift-detection.yml:
name: Infrastructure Drift Detection
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch:
jobs:
drift-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Configure your cloud provider authentication
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
# Run DriftHound
- name: Run drift detection
uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}Add these secrets to your GitHub repository:
DRIFTHOUND_URL- Your DriftHound API URL (e.g.,https://drifthound.example.com)DRIFTHOUND_TOKEN- Your DriftHound API token- Cloud provider credentials (AWS, GCP, Azure, etc.)
# Set the tool used across your repository
default_tool: terragrunt # terraform | opentofu | terragrunt
# Optional: Specify tool versions (or omit for latest)
tool_versions:
terraform: "1.6.0"
opentofu: "1.6.0"
terragrunt: "0.54.0"
# Note: If using Terragrunt, specify either terraform or opentofu version
# Terragrunt is a wrapper and requires one of these tools
# Define drift detection scopes
scopes:
- name: string # Required: Unique identifier
project: string # Required: Project name in DriftHound
environment: string # Required: Environment (production, staging, development, etc.)
directory: string # Required: Path to IaC files (relative to repo root)
tool: string # Optional: Override default_tool for this scope
tool_version: string # Optional: Override global tool version for this scope
slack_channel: string # Optional: Slack channel for notifications (e.g., #alerts)| Input | Required | Default | Description |
|---|---|---|---|
drifthound-url |
Yes | - | DriftHound API URL |
drifthound-token |
Yes | - | DriftHound API token |
config-file |
No | drifthound.yaml |
Path to configuration file |
environment |
No | - | β Filter scopes by environment (e.g., production) |
scope |
No | - | Run a single specific scope |
scope-filter |
No | - | Comma-separated list of scopes to run |
fail-on-drift |
No | false |
Fail workflow if drift is detected |
cli-version |
No | main |
drifthound-cli version (branch/tag/commit) |
cli-repo |
No | drifthoundhq/DriftHound |
Repository containing drifthound-cli |
working-directory |
No | . |
Working directory for the action |
| Output | Description |
|---|---|
drift-detected |
Whether drift was detected (true/false) |
results |
JSON summary of all drift check results |
scopes-run |
Number of scopes executed |
scopes-with-drift |
Number of scopes with drift detected |
Run all scopes sequentially:
- uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}Run scopes in parallel for faster execution. You can filter by environment:
jobs:
prepare-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: generate
uses: drifthoundhq/drifthound-action/matrix@v1
with:
environment: production # Generate matrix for production only
drift-check:
needs: prepare-matrix
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_PROD_ROLE }}
- uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}
scope: ${{ matrix.name }}This combines parallel execution with environment-specific authentication!
Run only specific scopes:
- uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}
scope-filter: 'core-infrastructure-prod,networking-prod'Fail the workflow if drift is detected:
- uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}
fail-on-drift: 'true'Use a different configuration file:
- uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}
config-file: 'infra/drift-config.yaml'Use action outputs in subsequent steps:
- name: Run drift detection
id: drift
uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}
- name: Process results
run: |
echo "Drift detected: ${{ steps.drift.outputs.drift-detected }}"
echo "Scopes checked: ${{ steps.drift.outputs.scopes-run }}"
echo "Scopes with drift: ${{ steps.drift.outputs.scopes-with-drift }}"
echo "Results: ${{ steps.drift.outputs.results }}"Authenticate to your cloud providers before running the DriftHound action:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Run drift detection
uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}Solution: Create separate jobs per environment with environment-specific authentication. See the Environment Authentication Guide for detailed patterns.
Quick example:
jobs:
production:
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_PROD_ROLE }}
- uses: drifthoundhq/drifthound-action@v1
with:
environment: production # Filter to production scopes- GCP: Use
google-github-actions/auth - Azure: Use
azure/login - Multiple providers: See examples/monorepo.yml
on:
schedule:
- cron: '0 */6 * * *' # Production every 6 hours
- cron: '0 9 * * *' # Staging daily at 9amon:
workflow_dispatch:
inputs:
environment:
description: 'Environment to check'
required: true
type: choice
options:
- production
- staging
- development
jobs:
drift-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}
environment: ${{ github.event.inputs.environment }}The action provides outputs that you can use in subsequent workflow steps:
- name: Run drift detection
id: drift
uses: drifthoundhq/drifthound-action@v1
with:
drifthound-url: ${{ secrets.DRIFTHOUND_URL }}
drifthound-token: ${{ secrets.DRIFTHOUND_TOKEN }}
# Example 1: Block deployment if drift detected
- name: Block deployment on drift
if: steps.drift.outputs.drift-detected == 'true'
run: |
echo "::error::Drift detected! Blocking deployment."
exit 1
# Example 2: Create GitHub Issue on drift
- name: Create issue on drift
if: steps.drift.outputs.drift-detected == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'π¨ Infrastructure Drift Detected',
body: `Drift was detected in ${{ steps.drift.outputs.scopes-with-drift }} scope(s).
**Details:**
- Total scopes checked: ${{ steps.drift.outputs.scopes-run }}
- Scopes with drift: ${{ steps.drift.outputs.scopes-with-drift }}
View details in [DriftHound](${{ secrets.DRIFTHOUND_URL }}).`,
labels: ['infrastructure', 'drift']
});
# Example 3: Add custom metrics/reporting
- name: Send metrics to monitoring system
run: |
curl -X POST https://your-metrics-endpoint.com/api/metrics \
-H "Content-Type: application/json" \
-d '{
"drift_detected": "${{ steps.drift.outputs.drift-detected }}",
"scopes_checked": ${{ steps.drift.outputs.scopes-run }},
"scopes_with_drift": ${{ steps.drift.outputs.scopes-with-drift }}
}'See the examples/ directory for complete workflow examples:
- environment-based.yml - β RECOMMENDED: Sequential checks with environment-specific auth
- matrix-with-environment.yml - β BEST PERFORMANCE: Parallel checks for one environment
- simple.yml - Basic sequential execution (single environment)
- matrix.yml - Parallel execution with matrix strategy
- monorepo.yml - Complex monorepo with multiple cloud providers
Complete guides and references:
- Quick Start Guide - Get started in 5 minutes
- Environment Authentication - Multi-environment setup guide
- Testing Guide - Testing strategy and how to run tests
- Contributing - Contribution guidelines
- CLI Improvements - Suggestions for drifthound-cli
- Changelog - Version history
This action is designed with monorepos in mind. You can:
- Define multiple scopes in
drifthound.yamlfor different projects/environments - Run checks in parallel using the matrix strategy
- Filter scopes to run only what's needed
- Mix tools (Terraform, OpenTofu, Terragrunt) in the same repository
- Configure different authentication per cloud provider
Example monorepo structure:
my-monorepo/
βββ drifthound.yaml # Central configuration
βββ .github/
β βββ workflows/
β βββ drift-detection.yml
βββ terraform/
β βββ aws-core/
β βββ aws-networking/
β βββ gcp-services/
βββ opentofu/
β βββ azure-storage/
βββ terragrunt/
βββ multi-region/
Problem: 401 Unauthorized from DriftHound API
Solution: Verify your DRIFTHOUND_TOKEN secret is correct and hasn't been revoked. Generate a new token if needed.
Problem: command not found: terraform
Solution: The action automatically installs tools based on the scopes being run. Ensure the tool field in your config matches exactly: terraform, opentofu, or terragrunt.
Note on Terragrunt: Terragrunt is a wrapper around Terraform or OpenTofu. If you use Terragrunt in any scope, the action will also install the underlying tool:
- If
tool_versions.terraformis specified, Terraform will be installed - If
tool_versions.opentofuis specified, OpenTofu will be installed - If neither is specified, Terraform will be installed by default
Problem: Scope 'my-scope' not found in configuration
Solution: Check the name field in your drifthound.yaml matches exactly what you're passing to scope or scope-filter.
Problem: Directory not found: ./terraform/core
Solution: Ensure the directory path in your config is relative to the repository root and exists.
Problem: Drift checks fail with Terraform errors
Solution:
- Verify cloud provider authentication is configured correctly
- Ensure Terraform state backend is accessible from GitHub Actions
- Check that required environment variables are set
- Review Terraform/OpenTofu/Terragrunt logs in the action output
Contributions are welcome! See CONTRIBUTING.md for detailed guidelines.
MIT
- Documentation: DriftHound Docs
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- DriftHound - The main DriftHound application
- drifthound-cli - Command-line interface for DriftHound