Provider Versions and the Lock File
Provider version constraints and the dependency lock file are what make a Terraform run reproducible. The constraint says which google provider versions are acceptable; .terraform.lock.hcl records the exact version and the cryptographic hashes that terraform init actually resolved. Together they pin behavior so a run is the same wherever it happens.
Skip either and the floor moves under you. Two engineers — or your laptop and the CI runner — can silently run different google provider builds, so a plan that is clean on one machine fails on another with no code change to explain it. This topic is the small discipline that closes that gap.
Version Constraints
The constraint lives in the version argument of the required_providers block. The operator that matters is the pessimistic one, ~>: a constraint of ~> 7.0 allows any 7.x release but refuses 8.0, so you pick up minor fixes and features automatically while a breaking major bump cannot sneak in.
terraform { required_providers { google = { source = "hashicorp/google" version = "~> 7.0" # 7.x yes, 8.0 no } } }
This lets init resolve 7.12 or 7.40 as they ship, but never 8.0, where arguments may be renamed or removed. The constraint is the guardrail; the next section is why it is not optional on the google provider specifically.
The google Provider's Cadence
The google provider ships frequent minor releases and occasional majors. A major bump — 6.x to 7.x — renames or removes arguments and changes resource behavior, exactly the kind of change that breaks a configuration mid-sprint if it arrives unannounced. Given how fast the provider moves, pinning the major version is not optional on a team.
Omit the constraint entirely and the next init on a fresh machine can grab whatever major is current — including one released yesterday. Pin ~> 7.0 and the major only changes when you decide to change it, on a branch, with a plan you have read.
The Lock File
The constraint says "7.x is fine"; the lock file says "this exact 7.x build, with these hashes." .terraform.lock.hcl records the precise version init selected and the platform hashes that verify the provider binary has not changed underneath you. Committing it makes every subsequent init — yours, a teammate's, CI's — resolve the identical build.
provider "registry.terraform.io/hashicorp/google" { version = "7.12.0" constraints = "~> 7.0" hashes = [ "h1:abc123...", "zh:def456...", ] }
The lock file pins 7.12.0 exactly, even though the constraint would permit any 7.x. Anyone who runs init against this committed file gets 7.12.0 and the same hashes, so the developer and CI cannot drift onto different builds. Treat the lock file as source-controlled, not as a scratch artifact.
Upgrading Deliberately
A plain init respects the lock file and changes nothing about the version. To move within the constraint you run terraform init -upgrade, which resolves the newest allowed version and rewrites the lock file. That rewrite is a diff — and you review it like any other code change, because it is the record of which provider behavior you are now running.
Treating -upgrade as a routine reflex is the mistake. Run it intentionally, read the lock-file diff it produces, and commit that change as its own reviewable step. Major upgrades especially — 7.x to 8.x — belong on a branch with a full plan review, never slipped in while you were doing something else.
Cross-Platform Hashes
A lock file can record hashes for more than one operating system, which matters the moment your team is not homogeneous. If the file holds only the developer's macOS hash and CI runs on Linux, CI fails hash verification on a provider it cannot validate — a confusing failure that has nothing to do with the configuration.
# Adds hashes for each listed platform to the lock file:
terraform providers lock \
-platform=darwin_arm64 \
-platform=linux_amd64
terraform providers lock with the platforms your laptops and CI runners use writes every needed hash into the lock file, so a Mac developer and a Linux runner both validate the same provider. Run it once when you set the constraint, and the cross-platform verification problem never appears.
- Omitting a version constraint and getting a
googlemajor upgrade on the nextinitthat renames arguments and breaks resources mid-sprint. - Not committing
.terraform.lock.hcl, so CI resolves a different provider build than the developer and a clean local plan fails in the pipeline. - A lock file holding only the developer's platform hash, then CI on Linux failing hash verification on a provider it cannot validate.
- Treating
init -upgradeas routine and not reviewing the lock-file diff that records exactly which provider behavior changed. - Slipping a major provider upgrade into an unrelated change instead of isolating it on a branch with a full plan review.
- Always set a pessimistic constraint (
~> 7.0) on thegoogleprovider and commit.terraform.lock.hclto version control. - Run
terraform init -upgradeintentionally, then review and commit the lock-file change as its own step. - Use
terraform providers lockto record hashes for every platform your team and CI run, so verification passes everywhere. - Upgrade the
googleprovider major version on a branch with a full plan review, never in passing. - Pin
googleandgoogle-betato the same major constraint so the GA and beta surface stay in step.
Knowledge Check
What does a constraint of ~> 7.0 on the google provider allow and forbid?
- It allows any 7.x release but forbids 8.0, so minor updates flow but a breaking major cannot
- It pins exactly version 7.0.0 and forbids every other release of the provider
- It allows any version at or above 7.0, including the 8.x and 9.x majors, treating the operator as a simple floor with no ceiling on the major component
- It forbids the entire 7.x line and forces the latest available provider
Why is committing .terraform.lock.hcl to version control important?
- It makes every
initresolve the identical provider build, so the developer and CI cannot drift onto different versions - It stores the provider's credentials so CI runners can authenticate to GCP, embedding the service-account token in the committed lock file for every pipeline to reuse
- It is required before Terraform can download any provider plugin at all
- It caches the entire provider binary itself inside the committed repository
Your lock file has only a macOS hash and CI runs on Linux. What happens and what is the fix?
- CI fails hash verification; run
terraform providers lockwith both platforms to record their hashes - CI silently downgrades to a Linux-only build of the provider with no warning at all
- Nothing breaks — provider hashes are fully platform-independent and portable
- CI rewrites the lock file automatically and commits the Linux hash for you, pushing the amended lock back to the branch so the next run already has both platforms
What is the safe way to move the google provider to a newer version?
- Run
terraform init -upgradeintentionally, review the lock-file diff, and isolate major bumps on a branch with a full plan - Delete the lock file before every run so
initalways grabs the newest published version - Hand-edit the version number directly inside the committed lock file and re-run init, trusting terraform to accept the new version and reuse the existing recorded hashes unchanged
- Remove the version constraint entirely so every
initauto-upgrades to whatever is newest
You got correct