
This post is part of a series on Terraform Drift Detection and Management: A Comprehensive Guide.
Infrastructure drift is inevitable. Someone makes a manual change in the AWS console during an incident, an auto-scaling event modifies a resource outside of Terraform’s control, or a teammate applies a hotfix directly to production. The question isn’t whether drift will happen. It’s whether you’ll catch it before it causes a compliance violation, a failed deployment, or a security incident.
Scheduled drift detection fixes this. It runs automated checks at regular intervals and compares your actual infrastructure state against what your Terraform or OpenTofu configuration declares. This guide walks through how to set it up across different approaches, from manual cron jobs to platform-native solutions.
For a broader overview of what drift is and why it matters, see our comprehensive guide to Terraform drift detection.
Running terraform plan manually to check for drift works in theory, but it falls apart in practice. Teams forget. Engineers context-switch. And the longer drift goes undetected, the harder it becomes to untangle what changed, who changed it, and whether it was intentional.
Scheduled detection takes people out of the loop. It catches drift within hours instead of weeks, surfaces changes while the context is still fresh, and leaves an audit trail that compliance teams actually care about. The gap between “we check for drift sometimes” and “we detect drift within 24 hours” is often what decides whether you pass or fail a SOC 2 audit. That’s also why teams pair drift detection with policy-as-code enforcement, so non-compliant changes get stopped before they’re ever applied.
The simplest approach uses Terraform’s built-in exit codes. When you run terraform plan -detailed-exitcode, it returns exit code 0 for no changes, 1 for errors, and 2 for detected drift. You can wrap this in a script and schedule it with cron or a CI/CD pipeline.
#!/bin/bash
# drift-check.sh
cd /path/to/terraform/config
terraform init -backend-config=prod.hcl -no-color
terraform plan -detailed-exitcode -no-color -out=drift.plan 2>&1
EXIT_CODE=$?
if [ $EXIT_CODE -eq 2 ]; then
echo "DRIFT DETECTED"
terraform show -no-color drift.plan | curl -X POST \\
-H 'Content-type: application/json' \\
--data '{"text": "Drift detected in production"}' \\
"$SLACK_WEBHOOK_URL"
elif [ $EXIT_CODE -eq 1 ]; then
echo "ERROR running plan"
fi
rm -f drift.plan
# Run drift check daily at 6 AM UTC
0 6 * * * /opt/scripts/drift-check.sh >> /var/log/drift-check.log 2>&1
This works for a handful of workspaces, but it doesn’t scale. You have to manage credentials for every provider, keep a separate script per workspace, handle state locking conflicts with production runs, and build your own alerting and reporting. By the time you’ve solved all of that, you’ve built a rough drift detection platform, which is exactly what dedicated tools already do.
A step up from cron is running drift checks through your CI/CD system. GitHub Actions, GitLab CI, and Jenkins all support scheduled triggers.
name: Drift Detection
on:
schedule:
- cron: '0 6 * * 1-5'
workflow_dispatch: {}
jobs:
detect-drift:
runs-on: ubuntu-latest
strategy:
matrix:
workspace: [networking, compute, database, monitoring]
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Check for drift
id: plan
run: |
cd workspaces/${{ matrix.workspace }}
terraform init
terraform plan -detailed-exitcode -no-color
continue-on-error: true
- name: Alert on drift
if: steps.plan.outcome == 'failure'
run: echo "Drift detected"
This is better than raw cron: you get logs, parallel execution across workspaces, and integration with your existing alerting. But you’re still managing credentials, paying for CI/CD minutes, and dealing with state lock contention when drift checks overlap with real deployments.
Scalr has built-in drift detection, so you skip the infrastructure overhead of the DIY approaches. Instead of building and maintaining scripts, you turn on drift detection at the environment level and Scalr handles the rest.
In Scalr you enable drift detection per environment instead of per workspace. That’s on purpose: it gives you consistent coverage across every workspace in an environment without configuring each one by hand. To turn it on:
There’s no script to maintain, no credentials to manage separately, and no state lock conflicts. Scalr coordinates drift checks with regular runs automatically.
When Scalr detects drift, the results show up in a dedicated Drift Detection tab, separate from your regular run history. So drift findings don’t get buried in a stream of plan/apply runs. You can also build custom dashboards that pull drift status together across all the workspaces in your organization, giving platform teams a single view of infrastructure health.
For real-time alerting, Scalr integrates with Slack and Microsoft Teams to notify your team when drift is detected. Rather than parsing CI/CD logs or monitoring email, your on-call engineer gets a chat message with the affected workspace and a direct link to review the changes.
Once drift is detected, Scalr presents three options directly in the UI: Ignore for expected changes, Sync State to update state to match reality, or Revert Infrastructure to roll back unauthorized changes. Scalr deliberately keeps a human in the loop for remediation, because fully automated rollbacks sound appealing until they revert an emergency scaling event at 3 AM.
For a deeper look at when to use each remediation strategy, see our guide to drift detection and remediation.
Cron + shell scripts work for small teams with fewer than 10 workspaces and simple provider setups.
CI/CD pipelines are a good fit for teams that already have mature pipeline infrastructure and want drift detection without adding another tool.
Platform-native detection makes sense once you’re managing dozens of workspaces or need visibility into drift across teams. The setup cost is near zero, and the ongoing maintenance cost is actually zero.
Whichever approach you pick, what matters most is that drift detection runs automatically and consistently. A check that runs every day catches problems that ad-hoc manual checks never will.
