Provider versions are pinned in required_providers and installed from a registry or mirror, but the operational work happens when the version moves.
An upgraded provider can reformat state, change attributes, or force replacement. A versioned provider that nobody upgrades drifts until a forced move happens mid-incident or mid-release.
This note is the operational discipline for keeping provider versions intentional.
Pin Deliberately
Every provider that matters should have an explicit constraint and a recorded version.
terraform {
required_version = ">= 1.0"
required_providers {
vsphere = {
source = "hashicorp/vsphere"
version = ">= 2.0"
}
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
The source and constraint live where the root declares intent. The installed version is what terraform providers shows.
Constraint Strategy
Constraints separate intent from the installed binary.
>= 2.0allows any newer version and makes plans drift when a new release lands.~> 5.0allows minor upgrades only (5.0.x) and is close to the operator’s intent: pick up bug fixes, avoid majors.~> 5.16allows patch-level upgrades and is the tightest safe window.- a specific version in constraints is rarely needed; lock files and environment mirrors already fix the exact install.
Prefer ~> for providers that can change behavior between minors, and leave a recorded reason for any provider allowed to float.
The Lock File
Terraform records the exact installed versions in .terraform.lock.hcl.
Commit the lock file. It fixes the provider set for everyone who runs terraform init in that root.
When the lock file is missing from source control:
- contributors install different provider versions.
- plans diverge by machine.
- the incident is only reproducible on some laptops.
Review the lock file like you review code: a change to it is a provider move.
Upgrade Workflow
Treat a provider upgrade as a change, not a background chore.
terraform init -upgrade
terraform providers # what is now installed
terraform plan # what the new version wants to do
Steps that keep the move reviewable:
- Inspect the current installed version.
- Select the target version and confirm it is a real choice, not just “latest”.
- Run
terraform init -upgradeand captureterraform providers. - Inspect changes to
.terraform.lock.hcl. - Run
terraform planand review every created, replaced, updated, and destroyed. - Look for schema and state-migration warnings in the plan and provider output.
- Promote through the same planning and apply gates as any change.
Plan Review Signals During An Upgrade
Certain plan output deserves extra attention:
- resources that previously planned no-op now plan replacement: schema change or state migration.
- attribute names or types changing on existing resources.
forces replacementon resources the upgrade should not have touched.- plan identity changes across environments from the same provider version.
Challenge the plan when the upgrade’s effect is bigger than the provider bump implies.
Version Drift Detection
The provider set can drift silently between environments. Check what is installed in each root against the lock file:
terraform providers
Compare the installed provider versions across roots and environments. A prod root on ~> 5.0 and a nonprod root on a different resolved minor is a source of environment-specific behavior.
Common Failure Modes
Floating Constraints
Constraints like >= 1.0 allow a major jump during a routine init -upgrade. The plan suddenly rewrites the resource set and nobody planned for it.
Uncommitted Lock File
The exact provider set is machine-local. Every workspace resolves differently, and upgrades land without review.
Latest-By-Reflex
The team upgrades because “latest is better”, without checking schema changes, state migrations, or the provider’s release notes. The plan discovers the damage after init.
Upgrade During An Incident
A provider bumped to fix a node issue is also a plan that can replace nodes. The recovery path and the upgrade path collide in one apply.
Uneven Environments
One environment resolves a newer patch and behaves differently. The team debugs a production symptom that a different provider version does not reproduce.
Review Checklist
- Is every provider pinned with
required_providersand a deliberate constraint? - Is
.terraform.lock.hclcommitted and reviewed? - Is each upgrade a reviewable change with a plan review?
- Does the plan show any schema, state, or replacement effects beyond the bump?
- Are provider versions consistent across environments?
- Are major and minor jumps scheduled instead of accidental?
- Is there a recovery note if provider state migration breaks a resource?
Practical Takeaway
Provider versioning is a change-management discipline, not a package manager detail.
Pin constraints deliberately, commit the lock file, treat each upgrade as a reviewable change, and watch the plan for behavior beyond the version bump. The version that resolves today is a decision, not a default.