Chapter 1: Foundations
Topic 05

Providers and the Registry

Concept

A provider is the plugin that translates your HCL into real API calls. The Terraform engine knows how to read configuration, build a dependency graph, and track state — but it knows nothing about Cloud Storage, Compute Engine, or BigQuery. All of that lives in the hashicorp/google provider, with a parallel hashicorp/google-beta for preview features that have not yet landed in the stable provider.

The Terraform Registry is where you declare which providers and versions a configuration needs, and terraform init is the command that downloads them into a local .terraform/ directory and records the exact resolved versions in a lock file. Get the declaring, pinning, and locking right and provider upgrades become a deliberate choice rather than a surprise that breaks a Friday plan.

What a Provider Does

When you write resource "google_storage_bucket" "raw", the google provider is the code that maps that block to the Cloud Storage API — it builds the create request, handles authentication with your credentials, retries on transient errors, reads the bucket back to populate state, and issues the delete when you remove the resource. The full create-read-update-delete lifecycle for every GCP resource type lives in the provider.

The engine, by contrast, never makes an API call itself. It decides what needs to happen and in what order, then hands each operation to the relevant provider. That split is why the same plan/apply workflow drives GCP, AWS, and a hundred other platforms — the engine is constant, and the provider is the part that knows a specific API.

Declaring Providers

You declare the providers a configuration depends on in a required_providers block inside the top-level terraform block. Each entry names a source — the Registry address, hashicorp/google — and a version constraint. The constraint is what protects you: ~> 7.0 permits 7.x but refuses an automatic jump to 8.0, where resource behavior can change.

Declaring the google provider with a version constraint
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"   # Registry address
      version = "~> 7.0"               # allow 7.x, block 8.0
    }
  }
}

With that block in place, terraform init resolves the constraint against what is published on the Registry, picks the newest version that satisfies it, and downloads the plugin. You declare intent once; init turns it into an installed binary.

The Registry as Source of Truth

The Registry at registry.terraform.io hosts every public provider and thousands of reusable modules, each with documentation versioned to match the release. The google provider documentation there is the final word on which arguments a resource accepts — over any tutorial, including this one, because arguments are added and removed across versions.

The discipline that follows is to read the docs pinned to the provider version you actually run. The page for google 7.x can list an argument that does not exist in 6.x, and copying a snippet from the wrong version produces an error that looks like your mistake when it is really a version mismatch. Match the doc version to your constraint and that whole class of confusion disappears.

google vs google-beta

There are two Google providers built from the same codebase. hashicorp/google exposes the generally-available API surface; hashicorp/google-beta exposes preview resources and fields that Google has shipped to its beta APIs but not yet promoted to GA. They are not rivals — you add google-beta when a specific resource or argument requires it, and you can use both providers in the same configuration at once, pointing individual resources at whichever one they need.

The thing to avoid is flipping your entire configuration to google-beta because one resource needed a preview field. Beta resources carry weaker stability guarantees, so you scope beta to exactly the resources that depend on it and leave everything else on the stable provider. The provider chapter covers running both together in depth; here it is enough to know beta is a normal provider you add deliberately, not a separate install to fear.

Two providers, one codebase
hashicorp/google
The generally-available surface, standard stability guarantees. The default for the whole config.
hashicorp/google-beta
Preview resources and fields not yet promoted to GA. Scope it to only the resources that need a beta field.

The Lock File

When init resolves your providers, it writes .terraform.lock.hcl recording the exact version and a set of checksums for each plugin. That file is what makes builds reproducible: commit it, and every machine — your laptop, a teammate's, CI — installs the identical provider version and verifies it against the recorded hashes rather than re-resolving the constraint and possibly picking up a newer release.

Treat the lock file like a dependency lock in any other language: it belongs in version control, and you change it on purpose. Run terraform init -upgrade when you actually want a newer provider, then review the lock-file diff as part of the same pull request — so a provider upgrade is a visible, reviewable event, not something that happened because someone re-ran init on a fresh checkout.

google vs google-beta

hashicorp/google — the generally-available provider, built from the same codebase as beta. Use it for everything that does not require a preview feature; it carries the standard GA stability guarantees and is the default for the whole configuration.

hashicorp/google-beta — exposes preview resources and fields not yet promoted to GA. Add it alongside the stable provider and scope it to only the specific resources that need a beta field, accepting weaker stability there rather than moving your whole config to beta.

Common Mistakes
  • Omitting a version constraint on the google provider, then getting a surprise major upgrade from 6.x to 7.x on the next init that changes resource behavior under you.
  • Forgetting to commit .terraform.lock.hcl, so CI resolves a different provider version than the laptop that wrote and tested the code.
  • Reading the wrong provider doc version on the Registry — using a 7.x argument against a pinned 6.x provider produces an error that looks like your bug but is a version mismatch.
  • Switching the entire configuration to google-beta because one resource needed a preview field, taking on weaker stability guarantees everywhere for no reason.
  • Treating google-beta as a dangerous separate install to avoid, then hand-rolling workarounds for a feature a one-line provider addition would have given you.
Best Practices
  • Always set a required_providers version constraint, using a pessimistic ~> 7.0 so patch and minor releases flow in but major upgrades do not happen silently.
  • Commit .terraform.lock.hcl to version control so every machine and CI install the identical provider version and verify it against the recorded checksums.
  • Read the Registry documentation pinned to the exact provider version you run, treating it as the source of truth over any tutorial.
  • Run terraform init -upgrade only when you intend to move versions, then review the lock-file diff in the same pull request.
  • Add google-beta only for the specific resources that require a preview feature, keeping everything else on the stable provider.
Comparable tools AWS · Azure providers the same Registry and lock-file model Pulumi packages provider plugins resolved per language ecosystem Config Connector CRDs the KRM analog of providers

Knowledge Check

What is the division of labor between the Terraform engine and the google provider?

  • The engine builds the graph and decides what to do; the provider maps each resource to a GCP API call
  • The provider parses the HCL files while the engine issues all of the real GCP API calls
  • The engine stores the state file while the provider stores the parsed configuration
  • They are one single binary with no real separation of responsibility between the graph and API call work

Why commit .terraform.lock.hcl to version control?

  • So every machine and CI install the identical provider version and verify it against recorded checksums
  • So Terraform can skip the init step entirely on every later run in that particular working directory
  • Because it stores your GCP service-account credentials securely for the whole team to share on checkout
  • Because the public Registry refuses to serve any providers without it present

When should you reach for google-beta instead of google?

  • For the specific resources that need a preview field, leaving the rest of the config on stable
  • For the whole configuration, since the beta provider is always newer and therefore strictly better
  • Only as a fallback for when you cannot install the stable google provider at all
  • Never — beta resources are forbidden in any production configuration

You omitted a version constraint and a fresh init pulled google 8.0, changing a resource's behavior. What would have prevented this?

  • A pessimistic constraint like ~> 7.0 that permits 7.x but blocks the jump to 8.0
  • Running apply instead of init so the version stays fixed
  • Deleting the .terraform/ plugin cache directory before every single run
  • Switching over to the google-beta provider, which simply never changes its version at all

You got correct