Chapter 2: The Google Provider, Projects & APIs
Topic 14

Provider Versions and the Lock File

Versioning

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.

A pessimistic constraint on the google provider
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.

A committed .terraform.lock.hcl entry
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.

Moving the provider version deliberately
constraint ~> 7.0
init -upgrade
review .terraform.lock.hcl

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.

Recording hashes for every platform the team runs
# 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.

Common Mistakes
  • Omitting a version constraint and getting a google major upgrade on the next init that 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 -upgrade as 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.
Best Practices
  • Always set a pessimistic constraint (~> 7.0) on the google provider and commit .terraform.lock.hcl to version control.
  • Run terraform init -upgrade intentionally, then review and commit the lock-file change as its own step.
  • Use terraform providers lock to record hashes for every platform your team and CI run, so verification passes everywhere.
  • Upgrade the google provider major version on a branch with a full plan review, never in passing.
  • Pin google and google-beta to the same major constraint so the GA and beta surface stay in step.
Comparable tools AWS provider the identical constraint and lock-file model package-lock.json · Cargo.lock language lockfiles as the mental model Pulumi version pinning on its provider plugins

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 init resolve 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 lock with 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 -upgrade intentionally, review the lock-file diff, and isolate major bumps on a branch with a full plan
  • Delete the lock file before every run so init always 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 init auto-upgrades to whatever is newest

You got correct