Skip to main content

OpenTofu

OpenTofu is the open-source infrastructure-as-code engine that plans and applies the platform's modules: it reads the configuration, compares it with the recorded state and the cloud, and changes only the difference. The GCP Enterprise Baseline and the GCP Web App and Data and ETL Blueprints pin OpenTofu 1.12.6 and run it through Terragrunt.

What it does​

A module is a folder of resources and variables. A provider is the plugin that talks to one API, here Google Cloud, Kubernetes or Helm. A plan shows the changes a configuration would make; an apply makes them and records the result in state, kept in a backend with a lock so two runs cannot write at once. Version constraints pin the engine and every provider so a run tomorrow behaves like a run today.

How BuiltForProd uses it​

One engine version. Every root.hcl sets terraform_version_constraint = ">= 1.12.6, < 2.0.0", and the workflows install 1.12.6 (TF_VERSION) with the setup-opentofu action.

Providers. root.hcl generates each unit's versions.tf, the only place providers are pinned:

GCP/acme-gcp-platform-baseline/root.hcl (lines 194-212)
generate "versions" {
path = "versions.tf"
if_exists = "skip"
contents = <<-EOF
terraform {
required_version = ">= 1.12.6"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 8.5"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 8.5"
}
}
}
EOF
}
RepositoryProviders
GCP Enterprise Baselinehashicorp/google and hashicorp/google-beta ~> 8.5; hashicorp/helm ~> 3.3 in github-runners
Web App Blueprinthashicorp/google ~> 8.5; hashicorp/helm ~> 3.3 and hashicorp/kubernetes ~> 3.2 in the cluster modules
Data and ETL Blueprinthashicorp/google and hashicorp/google-beta ~> 8.5

A module ships its own versions.tf only when it needs provider aliases or an extra provider, and then repeats the root pins exactly; scripts/check-module-versions.py fails a pull request that drifts. The generated provider blocks set user_project_override with the target project as billing project, so quota and billing land in the project being changed, and add every platform label as default_labels.

Modules. Every module is a thin local module: 31 in the Baseline, 10 in the Web App Blueprint and 5 in the Data and ETL Blueprint, with no registry modules. One module does one job (a VPC, a log bucket, a GKE cluster), and a unit instantiates it.

State. All repositories share one Cloud Storage bucket, acme-usw1-root-tfstate in the seed project, with OpenTofu's native locking and a prefix per unit; bucket IAM lets each repository identity write only its own prefix. Nothing in root.hcl calls gcloud or needs credentials to evaluate, so terragrunt stack generate, render and validate work offline.

Static checks. The plan workflow checks formatting with tofu fmt and runs tflint with the Google ruleset and Checkov; pre-commit adds tofu validate and Trivy; see Checkov, Trivy and tflint.

Terms you will see​

TermMeaning
ProviderThe plugin that talks to one API, such as hashicorp/google.
~> 8.5Any 8.x release from 8.5 on, never 9.0.
user_project_overrideBill and count quota against the project being changed.
State prefixThe folder in the state bucket that holds one unit's state.
Native lockingThe lock file OpenTofu writes beside the state in Cloud Storage.

Where to read more​