TerraformVI · Providers and the Provider EcosystemProduction Terraform
Provider Versioning and the Dependency Lock
What you'll learn
- Write provider version constraints using `=`, `>=`, `<`, and `~>`
- Explain the role of `.terraform.lock.hcl` and commit it to Git
- Upgrade providers with `terraform init -upgrade` and review the plan
- Lock the file for every platform the team uses
- Recognise the production cost of an unpinned provider
Prerequisites
None — start here.
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13
A pinned provider version is reproducible. An unpinned version
can change without notice. The control is two-fold: the
version constraint in required_providers and the resolved
version in .terraform.lock.hcl. Both belong in Git. The
lesson teaches the constraint operators, the lock file
semantics, and the upgrade procedure.
The constraint in required_providers
Every configuration that uses a provider declares the version it accepts:
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.80"
}
azurerm = {
source = "hashicorp/azurerm"
version = ">= 4.0, < 5.0"
}
github = {
source = "integrations/github"
version = "= 6.5.0"
}
}
}
Five operators are useful in production:
= 5.80.0— exact. No flexibility. Use when a specific version is the only one the team has validated.>= 5.80.0— at least this version. Usually paired with an upper bound.< 6.0.0— less than. Always pair>=with<when you want to avoid a major bump.~> 5.80— pessimistic. Only the rightmost component named in the constraint may increment.~> 5.80names the minor, so the minor may move: it accepts 5.80 and every later 5.x release, but not 6.0. Name the patch as well to tighten it —~> 5.80.0accepts 5.80.0 and later 5.80.x patches, but not 5.81.0.>= 5.0, < 6.0— explicit range. Useful when the team wants to lock to a major version without using~>.
The default for production is ~> <major>.<minor>.<patch>
for providers you track closely, because it admits patches and
nothing else. Use ~> <major>.<minor> (equivalently
>= <major>.<minor>, < <major+1>.0) when the team is content
to take new minors within the same major automatically. Avoid
the unpinned case:
# REJECTED: no version constraint.
required_providers {
aws = {
source = "hashicorp/aws"
# version = ???
}
}
The dependency lock file
After terraform init, the resolved versions live in
.terraform.lock.hcl:
# This file is maintained automatically by "terraform init".
# Manual edits may be lost - proceed with caution!
provider "registry.terraform.io/hashicorp/aws" {
version = "5.80.0"
hashes = [
"h1:abcd1234...",
"h1:efgh5678...",
"h1:ijkl9012...",
]
}
provider "registry.terraform.io/hashicorp/azurerm" {
version = "4.20.0"
hashes = [
"h1:mnop3456...",
]
}
The lock file records two things for each provider:
- The exact version resolved against the current constraint.
- The hashes of the binary on every platform Terraform has downloaded.
Two properties follow from this:
- Reproducibility. A colleague who clones the repository
and runs
initagainst the same lock file gets the same provider binaries. No surprise upgrades. - Integrity. Terraform re-verifies the binary against the hash on every run. A tampered binary produces a hash mismatch and the apply stops.
The lock file should be committed to Git. The .terraform/
working directory should not be:
.terraform/ ← gitignored
.terraform.lock.hcl ← committed
terraform.tfstate ← gitignored (or remote-backed)
terraform.tfstate.backup ← gitignored
*.tfplan ← gitignored
Locking for multiple platforms
A team with mixed workstations and a Linux CI runner needs hashes for every platform it uses. Run once on each:
# CONFIGURATION: lock for the three common platforms.
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_amd64 \
-platform=darwin_arm64
The lock file gains three hashes per provider. CI on Linux finds a matching hash and proceeds without re-downloading.
Upgrading providers
An upgrade is a managed change. The procedure:
# 1. Edit the constraint in versions.tf
vim versions.tf
# 2. Refresh the lock file
terraform init -upgrade
# 3. Run plan and inspect for unexpected changes
terraform plan -out=tfplan
# 4. Apply
terraform apply tfplan
init -upgrade is the only step that touches the lock file.
It downloads the new binary, updates the lock entry, and
leaves the configuration alone. The lock file change is the
audit trail — it is what code review sees.
Reading the upgrade plan
A provider upgrade can introduce silent changes. Three patterns to watch for in the plan:
1. Default changes. The new provider version changed a default for an attribute you did not set explicitly. The plan shows a change. If the change is the desired behaviour, accept it. If not, set the attribute explicitly.
2. New computed attributes. A resource has a new attribute that Terraform now records in state. The plan shows the attribute being added; the underlying resource is unchanged. Safe to accept.
3. Removed or renamed attributes. The new provider version removed or renamed an attribute you set. The plan fails with an error: “Unsupported attribute”. The fix is to update the configuration to use the new attribute name, or to delay the upgrade.
The provider’s release notes are the authoritative source. Reading them is not optional.
Production failure modes
Five failure modes recur:
1. Unpinned provider. The configuration has no
required_providers block at all. The provider resolves to
the latest version on every init. A new release can change
the schema between two plans. Fix: pin the constraint, commit
the lock file.
2. Lock file not committed. A developer runs init,
produces a lock file, but never commits it. CI on a fresh
clone resolves to a different version. Fix: commit the lock
file as part of the configuration change.
3. Stale lock file with platform mismatch. A macOS
developer commits a lock file with only darwin_arm64
hashes. The Linux CI runner cannot verify and downloads a new
hash, producing a noisy diff. Fix: lock for every platform
the team uses.
4. Forcing an upgrade without reading the changelog. A
team sets version = ">= 5.0" and init -upgrade jumps to
5.99. The plan shows 200 changes because the provider has
added and renamed attributes. Fix: read the release notes
before upgrading, and give the constraint a tighter ceiling —
~> 5.80.0 admits 5.80.x patches and nothing else.
5. Provider yanked from the registry. HashiCorp can yank
a release after publication. A lock file that pins the yanked
version fails init because the registry no longer serves
it. Fix: bump the constraint and re-lock.
Operational guidance
For a production Terraform estate:
- Pin every provider. No exceptions. The constraint is the first line of defence; the lock file is the second.
- Commit the lock file. Every commit that changes the constraint should change the lock file in the same commit. Reviewers can audit both.
- Lock for every platform the team uses. A single missing platform causes noisy CI diffs.
- Upgrade deliberately. An upgrade is a pull request. The PR contains the constraint change, the lock file change, and the plan output.
- Read the release notes before merging. Especially for major version bumps. Provider authors document breaking changes; the team should not be surprised by them.
What comes next
The next lesson is on provider aliases — how to configure multiple instances of the same provider for multi-region and multi-account work.
Verification
Knowledge check · 6 questions
Q1. What does the version constraint `~> 5.80` mean?
Q2. What is the role of `.terraform.lock.hcl`?
Q3. The `.terraform.lock.hcl` file should be committed to Git.
Q4. What does `terraform init -upgrade` do?
Q5. Which of the following are production risks of an unpinned provider? (Select all that apply.)
Q6. A team upgrades the AWS provider from 5.80 to 5.99 and the plan now proposes 50 unintended changes. The release notes mention new default values for several attributes. What is the right action?
Passing score: 75%. Answers are checked in this browser.