Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 8 min read

Fixing Terraform State Drift Before It Bites You

Drift is what happens between your code and reality when humans touch the console. Here's how I detect it, reconcile it, and stop it from causing failed applies.

  • #terraform
  • #drift
  • #state
  • #reconciliation
  • #devops
  • #automation

Drift is the gap between what your Terraform code says exists and what actually exists in the cloud. Someone clicks something in the console at 3pm on a Friday, and now your code and reality disagree. In 25 years I’ve learned that drift isn’t a question of if — it’s how much, and whether you find out before or after it breaks an apply.

Here’s how I keep drift under control.

Where drift comes from

Drift has a handful of usual suspects:

  • Console cowboys — an engineer fixes something manually during an incident and never codifies it.
  • Other automation — autoscalers, operators, or backup tools mutating resources Terraform thinks it owns.
  • Provider-side defaults — the cloud filling in a field you didn’t specify, which Terraform then wants to “correct.”

The first two are real drift you must reconcile. The third is often noise you need to teach Terraform to ignore.

Detecting drift on a schedule

Don’t wait for an apply to discover drift. Run a read-only detection plan on a schedule:

terraform plan -detailed-exitcode -refresh-only

The -detailed-exitcode flag returns 2 when there are changes, which makes it trivial to wire into CI:

- name: Drift check
  run: |
    terraform plan -detailed-exitcode -refresh-only || \
      [ $? -eq 2 ] && echo "DRIFT DETECTED" && exit 1

I run this nightly across the estate. A drift report every morning beats a surprise during a deploy.

Reading a refresh-only plan correctly

-refresh-only is the key tool people underuse. It updates state to match reality without proposing to change reality. The output tells you precisely what changed out-of-band:

~ resource "aws_security_group" "api" {
    ~ ingress = [
        + { from_port = 22, cidr_blocks = ["0.0.0.0/0"] },
      ]
  }

That’s not Terraform wanting to add an SSH rule — that’s Terraform telling you someone already did, in the console. Now you decide: is this a legitimate change to codify, or an unauthorized one to revert?

Reconciling: codify or revert

Once you know what drifted, you have two honest choices:

  • Codify it. The manual change was correct — add it to your HCL so code matches reality. Then a normal apply is a no-op.
  • Revert it. The change was unauthorized or wrong — run a standard apply and let Terraform put reality back to what the code says.

What you must not do is ignore it. Unreconciled drift compounds. Six months of “we’ll deal with it later” turns a five-minute reconcile into a multi-day archaeology project.

To accept the current reality into state without changing infrastructure:

terraform apply -refresh-only

Taming provider-side noise

Some drift isn’t real. A cloud provider sets a default tag or normalizes a value, and Terraform wants to “fix” it every plan. Silence that with ignore_changes:

resource "aws_autoscaling_group" "workers" {
  desired_capacity = 3
  lifecycle {
    ignore_changes = [desired_capacity]
  }
}

Here I’m telling Terraform: the autoscaler owns desired_capacity, stop trying to reset it. Use this surgically — over-applying ignore_changes blinds you to real drift.

Where AI helps with drift

A refresh-only plan over a large estate can be hundreds of lines. I paste it in and ask: “Group these drifted resources into ‘looks like a real human change worth codifying’ versus ‘provider default noise I should ignore_changes.’” It’s genuinely good at that triage, which saves me reading every diff line by line.

I’ll also ask it to draft the ignore_changes lifecycle blocks once I’ve decided what’s noise. For the recurring patterns, I keep a set of Terraform prompts handy. And I run reconciliation PRs through our Code Review tool so a second set of eyes confirms I’m codifying the right thing, not rubber-stamping an unauthorized console change.

The hard rule stands: AI reads the plan and proposes; I run the commands.

Prevent the drift in the first place

The real fix is cultural and technical:

  • Lock down console write access in production. If humans can’t click, they can’t drift.
  • Require change-by-code with break-glass as the rare exception that gets reconciled same-day.
  • Run scheduled drift detection so the feedback loop is hours, not months.

The takeaway

Drift is inevitable, but surprise drift is a choice. Detect it on a schedule with -refresh-only, reconcile every finding deliberately, and silence true provider noise with surgical ignore_changes. Catch it nightly and it’s a chore. Catch it during an apply and it’s an incident.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.