07/07/2026
Terraform on GCP: provision a complete three-tier architecture β VPC, Cloud SQL, GKE cluster, and Cloud Run β with repeatable Infrastructure as Code
ποΈ Terraform
π VPC
βΈοΈ GKE
π IaC
ποΈ Clicking through the GCP Console to build infrastructure works once. But clicking it again for staging, again for production, and again when disaster recovery is needed creates environments that drift apart silently. Terraform on GCP lets you define your entire architecture once and provision it identically β every time. Here's a complete production-pattern Terraform configuration.
π PROJECT STRUCTURE:
βββββββββββββββββββββββββββββββββββββ
gcp-infra/
βββ main.tf # Root module β calls child modules
βββ variables.tf # Input variables
βββ outputs.tf # Outputs (cluster endpoint, DB IP, etc.)
βββ versions.tf # Provider versions pinned
βββ backend.tf # Remote state in GCS bucket
βββ terraform.tfvars # Environment-specific values
βββ modules/
βββ networking/ # VPC, subnets, firewall rules
βββ gke/ # GKE cluster + node pools
βββ cloudsql/ # Cloud SQL instance + databases
βββ iam/ # Service accounts + bindings
βββββββββββββββββββββββββββββββββββββ
π BACKEND CONFIGURATION (remote state in GCS):
βββββββββββββββββββββββββββββββββββββ
# backend.tf
terraform {
backend "gcs" {
bucket = "my-project-terraform-state"
prefix = "production/gke-infra"
}
}
# Create the state bucket (one-time setup)
gsutil mb -l europe-west1 gs://my-project-terraform-state
gsutil versioning set on gs://my-project-terraform-state
βββββββββββββββββββββββββββββββββββββ
π VPC MODULE:
βββββββββββββββββββββββββββββββββββββ
# modules/networking/main.tf
resource "google_compute_network" "vpc" {
name = "${var.project_name}-vpc"
auto_create_subnetworks = false # Always use custom mode
routing_mode = "GLOBAL"
}
resource "google_compute_subnetwork" "gke_subnet" {
name = "${var.project_name}-gke-subnet"
ip_cidr_range = "10.0.0.0/20"
region = var.region
network = google_compute_network.vpc.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.100.0.0/14" # /14 = ~250k pod IPs
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.104.0.0/20"
}
private_ip_google_access = true # Reach GCP APIs without public IP
}
resource "google_compute_router" "router" {
name = "${var.project_name}-router"
region = var.region
network = google_compute_network.vpc.id
}
resource "google_compute_router_nat" "nat" {
name = "${var.project_name}-nat"
router = google_compute_router.router.name
region = var.region
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}
βββββββββββββββββββββββββββββββββββββ
π GKE MODULE:
βββββββββββββββββββββββββββββββββββββ
# modules/gke/main.tf
resource "google_container_cluster" "primary" {
name = "${var.project_name}-cluster"
location = var.region
enable_autopilot = true # Autopilot β Google manages nodes
network = var.vpc_id
subnetwork = var.subnet_id
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
private_cluster_config {
enable_private_nodes = true # Nodes have no public IPs
enable_private_endpoint = false # Control plane reachable publicly
master_ipv4_cidr_block = "172.16.0.0/28"
}
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
}
βββββββββββββββββββββββββββββββββββββ
π THE WORKFLOW THAT MAKES THIS PRODUCTION-READY:
βββββββββββββββββββββββββββββββββββββ
terraform init # Download GCP provider
terraform plan # Preview: "15 resources to add"
terraform apply # Provision everything
terraform destroy # Tear down completely (for dev environments: run at end of day)
βββββββββββββββββββββββββββββββββββββ
β The same Terraform code run with different terraform.tfvars creates identical staging and production environments β guaranteed consistency, zero configuration drift ποΈ
ποΈ Are you managing your GCP infrastructure with Terraform, Deployment Manager, Pulumi, or the Console? Drop your IaC tool and the GCP resource that was hardest to Terraform correctly!