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

Surviving Terraform Provider Version Upgrades

Major provider upgrades break plans in subtle ways across a large estate. Here's how I roll them out incrementally with lock files, pins, and read-only validation.

  • #terraform
  • #providers
  • #upgrades
  • #versioning
  • #lockfile
  • #devops

A major version bump of the AWS provider can quietly change resource defaults, rename arguments, and turn a no-op plan into a wall of unexpected diffs. Across a large estate with dozens of workspaces, an uncoordinated provider upgrade is how you spend a week chasing surprise destroys. After 25 years I treat provider upgrades like database migrations: deliberate, staged, and reversible.

Here’s my playbook.

Pin providers; don’t float them

The root cause of upgrade pain is floating versions. If your config says >= 5.0, every terraform init on a fresh machine can grab a newer provider with different behavior. Pin with a pessimistic constraint:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }
}

~> 5.40 allows patch and minor bumps within 5.x but never jumps to 6.0 by accident. Major upgrades become a deliberate, reviewed change — exactly what you want.

Commit the lock file

The .terraform.lock.hcl file records the exact provider versions and checksums for the whole team. Commit it. Without it, CI and your laptop can resolve different versions and produce different plans. With it, everyone is locked to the same provider until you intentionally upgrade:

terraform init -upgrade   # bumps within constraints, rewrites lock file

That -upgrade is the only thing that should move your providers, and it should produce a reviewable diff to the lock file.

Read the upgrade guide before the code

Every major provider ships an upgrade guide listing breaking changes. Read it first. The provider maintainers tell you precisely which arguments were renamed, which defaults changed, and which resources need attention. Skipping this and discovering breakage via failed plans is doing it the hard and slow way.

Stage the rollout, never big-bang it

Across a large estate, upgrade incrementally:

  1. One low-stakes workspace first — a dev environment. Bump the constraint, init -upgrade, and run a plan.
  2. Read the plan carefully. A clean plan means defaults didn’t shift. Surprise diffs mean the new provider computes something differently — investigate before proceeding.
  3. Fix forward. Update deprecated arguments, add ignore_changes for benign default shifts.
  4. Promote the upgrade to staging, then production, one tier at a time.

Never bump every workspace in one PR. If something breaks, you want a small blast radius and a clear culprit.

Validate read-only before you apply

The whole upgrade can be validated without touching infrastructure. A clean plan after init -upgrade is your green light:

terraform init -upgrade
terraform plan

If the plan shows zero changes, the new provider is behaviorally compatible for that workspace. If it shows changes, each one is a decision: codify it, ignore it, or hold the upgrade.

Where AI accelerates upgrades

Reading a long upgrade guide and mapping it onto your actual code is tedious, and that’s where AI helps. I paste the provider’s upgrade guide alongside a module and ask: “Which of these breaking changes apply to this configuration, and what edits do I need?” It reliably points me at the renamed arguments and changed defaults that affect me, instead of the whole list.

I also have it explain surprise plan diffs after an upgrade: “Why would the new provider want to change this field?” Usually it’s a default that moved. I keep these as Terraform prompts and route the upgrade PR through our Code Review tool to confirm the deprecated-argument fixes are complete.

AI reads guides and proposes edits; I run the plans and applies.

Have a rollback path

Because you committed the lock file and pinned versions, rollback is easy: revert the lock file and constraint, init, and you’re back on the old provider. If state was already mutated, your versioned backend gives you the prior state. Never start an upgrade you can’t reverse.

The takeaway

Provider upgrades break estates when they’re floating and big-bang. Pin with ~>, commit the lock file, read the upgrade guide, and roll out one workspace at a time with read-only plans as your gate. Treat each surprise diff as a decision, keep a rollback path, and a major provider bump becomes a routine staged migration instead of a fire drill.

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.