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

The Google Provider: google vs google-beta

ProvidersTooling

The google provider is configured once per project and region context and reused by every resource in the configuration. You set project, region, and zone in one provider block, and every google_* resource inherits those defaults unless it overrides them. Alongside it sits google-beta, built from the same codebase by the same maintainers, exposing preview fields and resources that have not reached general availability yet.

The two providers are not an either/or choice. You declare both in required_providers and reach for google-beta only on the specific resources that need a preview feature — the rest of your configuration stays on stable GA surface. Getting this split right, plus two GCP-specific knobs the provider exposes (the quota project and provider-wide default labels), is the whole of this topic.

Provider Configuration

The provider block is where the defaults live. project names the project resources are created in, region sets the default region for regional resources, and zone sets the default zone for zonal ones. A resource that does not specify its own location inherits these — so a single Compute instance with no zone argument lands in us-central1-a if that is the provider default.

An explicit provider block beats relying on ambient environment defaults. The google provider will read GOOGLE_PROJECT and friends from the environment if you let it, but that makes the configuration depend on whatever shell ran it — portable in theory, machine-dependent in practice. Pin the values in code and the same checkout behaves identically on every machine.

An explicit provider block for hatch-pipeline-dev
provider "google" {
  project = "hatch-pipeline-dev"
  region  = "us-central1"
  zone    = "us-central1-a"
}

Every resource in this configuration now defaults to project hatch-pipeline-dev, region us-central1, and zone us-central1-a. Nothing reads the environment, so the developer's laptop and the CI runner produce the same plan.

google vs google-beta

The two providers share maintainers, authentication, and most of their surface — the difference is what they expose. The google-beta provider carries fields and resources that the GA google provider does not yet, because those features are still in preview on GCP. Over time, features graduate: a field that was beta-only becomes available on the GA provider, and you migrate the resource back to google.

This matters because a beta-only field simply does not exist on the GA provider. Set it on a resource that runs through google and the plan errors with an unknown-argument message. The fix is not to set the field — it is to point that one resource at google-beta, which is the next section.

google vs google-beta
google
GA, stable surface with strong stability guarantees — though a major provider upgrade can still change or remove a field. The default for everything.
google-beta
Same codebase, preview-only fields not yet GA. Opt in per-resource; migrate back once the feature graduates.

Using Both at Once

You declare both providers in required_providers, then mark the specific resources that need preview surface with provider = google-beta. Everything else stays on GA by default. This is the correct pattern: opt individual resources into beta, never the whole configuration.

Both providers declared; one resource on beta
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 7.0"
    }
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "~> 7.0"
    }
  }
}

resource "google_workstations_workstation_cluster" "dev" {
  provider = google-beta   # this resource needs a preview-only field
  workstation_cluster_id = "hatch-dev-cluster"
  location               = "us-central1"
  network                = google_compute_network.main.id
  subnetwork             = google_compute_subnetwork.main.id
}

The provider = google-beta line is the only thing that routes this resource through the beta plugin. Drop it and the resource runs on GA, where the preview field would error. Resources without that line keep using google, so your stable infrastructure never touches preview surface.

The Quota and Billing Project

GCP has a knob with no AWS analog: which project gets billed for an API call and counts against its quota. By default that is the project the resource lives in, but cross-project operations — a resource in one project reading something in another — can need an explicit quota project. The provider's user_project_override and billing_project control this.

When user_project_override = true, the provider sends billing_project as the project whose quota and billing apply to API calls made with user credentials. Skip this on a cross-project call and you can hit a userProjectMissing error or have calls billed to a project you did not intend. Set it deliberately whenever a resource reaches across project boundaries.

Default Labels

The provider's default_labels argument stamps a set of labels onto every resource it manages, in one place instead of repeating them per-resource. Labels are GCP's key-value metadata for filtering, cost attribution, and policy — and putting team and environment on the provider means every bucket, instance, and dataset carries them without you remembering each time.

Provider-wide default labels
provider "google" {
  project = "hatch-pipeline-dev"
  region  = "us-central1"

  default_labels = {
    team        = "data-platform"
    environment = "dev"
    managed_by  = "terraform"
  }
}

Every resource this provider creates now carries team, environment, and managed_by labels. A resource can still add its own labels, which merge with these — but the common three are declared once, so cost reports and label-based policies see them consistently across the whole project.

google vs google-beta

google — the GA, stable provider and the default for everything. Its surface is supported for the long term and its fields do not vanish under you. Use it for all resources unless a specific one needs a preview-only field or resource type.

google-beta — the same provider built from the same codebase, exposing preview surface that has not reached GA. Set provider = google-beta per-resource, only where a field or resource type is beta-only, and track those resources so you can migrate them back to google once the feature graduates.

Common Mistakes
  • Switching the entire configuration to google-beta because one resource needed a preview field — you opt every resource into preview surface and unstable fields you never asked for.
  • Setting a beta-only field on a resource that runs through the GA google provider — the plan errors with an unknown-argument message because that field does not exist on GA.
  • Relying on GOOGLE_PROJECT and other GOOGLE_* environment variables for project and region instead of the provider block — the configuration becomes non-portable and behaves differently per machine.
  • Making a cross-project API call without setting a quota project, then hitting userProjectMissing or billing the call to the wrong project.
  • Repeating the same team and environment labels on every resource by hand instead of using default_labels — some resources drift and slip out of cost reports.
Best Practices
  • Set project, region, and zone explicitly in the provider block; do not depend on ambient GOOGLE_* environment defaults.
  • Add google-beta per-resource with provider = google-beta, only where a feature requires it, and track those resources for GA migration.
  • Use the provider's default_labels to stamp team, environment, and managed_by in one place rather than per-resource.
  • Set user_project_override and billing_project deliberately whenever a resource reads or writes across project boundaries.
  • Pin both google and google-beta to the same major version in required_providers so the GA and beta surface stay in step.
Comparable tools AWS provider default_tags and provider aliases for the same per-resource routing Azure provider the features block and preview feature flags Pulumi provider config objects and explicit provider arguments Config Connector applies GCP resources through the Kubernetes API, no beta-provider split

Knowledge Check

One resource in your configuration needs a preview-only field. What is the correct way to use google-beta?

  • Declare both providers and set provider = google-beta only on that one resource, leaving the rest on GA
  • Switch the whole configuration's default provider over to google-beta for every resource
  • Add the preview-only beta field directly to the GA google provider block, where the stable provider will read it and quietly forward the call to the beta backend
  • Carve out a separate Terraform working directory just for that one beta resource

What does the provider's quota/billing project (user_project_override + billing_project) control?

  • Which project's quota and billing apply to API calls, which matters for cross-project operations
  • Which region resources default to when no region argument is specified anywhere, overriding the provider block's own region for every API call the run makes
  • Whether the provider exposes the GA surface or the preview beta surface
  • The maximum number of resources the provider is permitted to create in one apply

Why set project and region in the provider block instead of relying on GOOGLE_* environment variables?

  • The configuration stays portable and produces the same plan on every machine regardless of shell environment
  • Environment variables like GOOGLE_PROJECT are not supported by the google provider at all
  • It is the only supported way to enable and use any google-beta preview resources
  • It makes every apply run faster by skipping the credential discovery step that the provider would otherwise repeat for each resource it touches in the run

What does the provider's default_labels argument do?

  • Stamps a common set of labels onto every resource the provider manages, in one place
  • Renames every resource the provider manages using a label-based naming scheme
  • Restricts which resource types the provider is allowed to create in the project
  • Sets the default region and zone applied to every labelled resource, so the placement of resources follows the label set rather than the provider block

You got correct