Detecting Infrastructure Drift๏ƒ

Audience: Operators who want to know if live infrastructure has diverged from what the Terraform code declares โ€” before running a full deploy.


What is drift?๏ƒ

Drift is any difference between the current state of your infrastructure (recorded in the Terraform state file) and what your Terraform configuration says it should be.

Drift happens when:

  • Someone edits a resource directly in the cloud console.

  • An upstream provider makes an automatic change (e.g. a managed certificate renewal that adds unexpected tags).

  • A previous pipeline failed mid-apply, leaving resources in a partially-updated state.

strata deploy drift surfaces these differences without making any changes.


Quick start๏ƒ

strata deploy drift run -f deploy/deploy-prd.yaml

When drift is found, the output looks like:

๐Ÿ”  Checking drift for 2 stage(s) (threshold: info)โ€ฆ

  Stage: infrastructure
  ADDRESS                                               ACTION    SEVERITY    CHANGED ATTRIBUTES
  ----------------------------------------------------  --------  ----------  ------------------------------
  azurerm_network_security_rule.allow_http              [~]       ๐Ÿ”ด critical  priority, access
  azurerm_virtual_machine.web01                         [~]       ๐ŸŸ  high      os_disk_size_gb
  azurerm_app_service.api                               [~]       ๐ŸŸก medium    app_settings

  Summary: 3 change(s) โ€” ๐Ÿ”ด 1 critical  ๐ŸŸ  1 high  ๐ŸŸก 1 medium  ๐Ÿ”ต 0 low  โšช 0 info

Exit code 3 is returned when drift is found at or above the configured severity threshold. Exit code 0 means no drift (or all drift is below the threshold).


How it works๏ƒ

For each deployment stage:

  1. terraform init โ€” ensure providers are up to date.

  2. terraform plan -detailed-exitcode โ€” compare state to configuration.
    Exit 0 = no changes, exit 2 = changes present.

  3. terraform show -json โ€” decode the plan to get structured resource changes.

  4. Classify each changed resource by severity using built-in + workspace rules.

  5. Persist history at .strata/drift/{deployment}.drift.json.

No resources are modified. No plan file is saved to the build directory.


Severity levels๏ƒ

Level

What it means

๐Ÿ”ด critical

Security-sensitive: network security rules, IAM roles, key vault policies

๐ŸŸ  high

Core infrastructure: VM sizes, Kubernetes clusters, virtual networks

๐ŸŸก medium

Configuration: app settings, monitoring rules, kubernetes workloads

๐Ÿ”ต low

Cosmetic: tags, descriptions

โšช info

Data sources, null resources, bookkeeping resources

Tag changes are always low regardless of resource type โ€” they match the attribute-level tags rule, which takes priority over any resource-type rule.


Command reference๏ƒ

strata deploy drift is a subgroup with three subcommands:

strata deploy drift run         # detect drift (was: strata deploy drift)
strata deploy drift acknowledge # suppress a known-drifted address
strata deploy drift history     # view run history and acknowledged addresses

drift run flags๏ƒ

strata deploy drift run -f <FILE> [OPTIONS]

Flag

Default

Description

-f / --file

required

Path to the deployment YAML file

--stage NAME

all stages

Run drift detection on one stage only

--severity LEVEL

info

Minimum severity for exit code 3 (critical/high/medium/low/info)

--baseline

off

Acknowledge all detected drift as baseline; always exits 0

--output FORMAT

console

console or json

--verbose

off

Show terraform output lines


Using severity thresholds๏ƒ

By default, any drift (including tag changes) triggers exit code 3. Use --severity to ignore low-signal changes in CI pipelines:

# Fail only on security-sensitive or core-infrastructure changes
strata deploy drift run -f deploy/deploy-prd.yaml --severity high

# Fail on anything medium or above (ignore tags, data sources)
strata deploy drift run -f deploy/deploy-prd.yaml --severity medium

Exit code 0 is returned when all detected drift is below the threshold. The drift is still reported in the output โ€” the threshold only controls the exit code.


Checking one stage๏ƒ

strata deploy drift run -f deploy/deploy-prd.yaml --stage infrastructure

Useful when a specific stage is suspected to have drift and you donโ€™t want to run terraform init for every stage.


Setting a baseline๏ƒ

After a known-good deploy, reset the drift history so age tracking starts from now:

strata deploy drift run -f deploy/deploy-prd.yaml --baseline

This runs drift detection, shows the report, acknowledges every detected entry, clears the run history, and records a baseline_at timestamp. Future checks will treat those resources as having first_detected from the next run they appear in.

--baseline always exits 0, regardless of what drift was found.


Acknowledging expected drift๏ƒ

Some resources drift intentionally โ€” auto-scalers change replica counts, policy agents add tags. Rather than resetting the entire baseline, suppress individual addresses:

# Suppress one address
strata deploy drift acknowledge -f deploy/deploy-prd.yaml \
    --address "azurerm_autoscale_setting.web" \
    --reason "auto-scaler managed โ€” expected drift"

# Re-enable reporting for an address
strata deploy drift acknowledge -f deploy/deploy-prd.yaml \
    --address "azurerm_autoscale_setting.web" \
    --remove

Acknowledged addresses are excluded from the report and do not affect the exit code. They remain suppressed until explicitly removed or until the baseline is reset.

drift acknowledge flags๏ƒ

Flag

Required

Description

-f / --file

yes

Deployment YAML to identify the deployment

--address ADDR

yes

Terraform resource address (e.g. azurerm_autoscale_setting.web)

--reason TEXT

no

Explanation stored in history for audit purposes

--remove

no

Remove a previously added acknowledgement


Viewing drift history๏ƒ

strata deploy drift history -f deploy/deploy-prd.yaml
strata deploy drift history -f deploy/deploy-prd.yaml --last 5

Shows:

  • A table of recent drift-check runs with their timestamps and drifted resource counts.

  • A list of currently acknowledged (suppressed) addresses.

drift history flags๏ƒ

Flag

Default

Description

-f / --file

yes

Deployment YAML to identify the deployment

--last N

10

Number of most-recent runs to display

--output

console

console or json


JSON output for CI๏ƒ

strata deploy drift run -f deploy/deploy-prd.yaml --output json

Output shape:

{
  "deployment": "my-deployment",
  "checked_at": "2026-07-06T09:00:00Z",
  "stages_checked": ["infrastructure", "services"],
  "has_drift": true,
  "max_severity": "critical",
  "summary": {
    "critical": 1, "high": 0, "medium": 1, "low": 0, "info": 0, "total": 2
  },
  "entries": [
    {
      "address": "azurerm_network_security_rule.allow_http",
      "resource_type": "azurerm_network_security_rule",
      "action": "update",
      "severity": "critical",
      "stage": "infrastructure",
      "changed_attributes": ["priority", "access"],
      "before": { "priority": 100, "access": "Allow" },
      "after":  { "priority": 200, "access": "Deny"  },
      "first_detected": "2026-07-05T08:00:00Z",
      "consecutive_checks": 2
    }
  ]
}

Sensitive values (flagged by Terraform) are replaced with "(sensitive)" so they are never written to history or output.


Drift history๏ƒ

Every run appends to .strata/drift/{deployment}.drift.json. This file tracks:

  • first_detected โ€” timestamp of the first run that found this resource drifting.

  • last_detected โ€” timestamp of the most recent run that found it drifting.

  • consecutive_checks โ€” how many consecutive runs found it in a drifted state.

consecutive_checks is reset to 0 when a resource stops drifting (e.g. after a deploy or a manual fix). It is never overwritten: if a resource was first detected 30 runs ago, that timestamp is preserved.

The history file is automatically added to .gitignore.


Customising severity rules๏ƒ

To override severity for specific resource types in your workspace, create .strata/drift_rules.yaml. Workspace rules take priority over the built-in rules:

# .strata/drift_rules.yaml
rules:
  # Our team considers tag drift worth tracking as medium (not low)
  - attribute: "tags"
    severity: medium

  # We manage this resource manually โ€” treat any drift as info only
  - resource_type: "azurerm_management_lock"
    severity: info

defaults:
  severity: medium

Rules are matched in this order:

  1. Attribute-level rules (most specific โ€” any resource with this attribute).

  2. Resource-type rules (exact or glob, e.g. azurerm_vm*).

  3. defaults.severity (catch-all).

Workspace rules are prepended to the built-in rule list, so they are evaluated first. You cannot remove built-in rules; you can only add higher-priority rules that match before them.


Using drift in CI๏ƒ

Add a drift check step before every deploy to catch out-of-band changes:

# .github/workflows/deploy.yml (example)
- name: Check for drift
  run: strata deploy drift run -f deploy/deploy-prd.yaml --severity high
  continue-on-error: true   # report drift but don't block the deploy

- name: Deploy
  run: strata deploy run -f deploy/deploy-prd.yaml --force

Or fail the pipeline on critical drift:

- name: Fail on critical drift
  run: strata deploy drift run -f deploy/deploy-prd.yaml --severity critical

Nightly scheduled drift check๏ƒ

A reusable GitHub Actions workflow is provided at .github/workflows/drift-check.yml. Use it directly:

# .github/workflows/nightly.yml
jobs:
  drift:
    uses: ./.github/workflows/drift-check.yml
    with:
      deployment: deploy/production.yaml
      severity: high
    secrets: inherit

The workflow:

  • Runs nightly at 02:00 UTC.

  • Posts a Slack notification when critical/high drift is found (optional โ€” set SLACK_DRIFT_WEBHOOK secret).

  • Uploads drift-report.json as an artifact (kept 90 days).

  • Exits 3 when drift at or above severity is detected.


Exit codes๏ƒ

Code

Meaning

0

No drift found, or all drift is below --severity threshold

1

Execution error (terraform failure, auth problem, missing build)

3

Drift found at or above --severity threshold


Prerequisites๏ƒ

  • Build artifacts must exist (strata build run must have run first).

  • Terraform must be on PATH (strata tools status to verify).

  • Authentication to the Terraform backend must be configured (same credentials required by terraform plan โ€” e.g. az login for Azure, AWS_* env vars for AWS).