Terraform
1. Overview
All GCP infrastructure is provisioned from infra/tf/gcp. The configuration is module-based, and Terrakube supplies the environment-specific variables and remote state for the gateway production workspace and gateway-staging staging workspace.
2. Directory Structure
infra/tf/gcp/
├── main.tf # Providers, backend, root resources, module wiring
├── variables.tf # Shared inputs and safe defaults
├── outputs.tf # Root outputs
├── backend-prod.hcl # Terrakube workspace: gateway
├── backend-staging.hcl # Terrakube workspace: gateway-staging
├── terrakube-imports.tf # Imported Terrakube WIF resources
├── removed-secret-versions.tf
└── modules/
├── apphub/
├── artifact-registry/
├── cloud-run/
├── device-runtime/
├── firebase/
├── iam/
├── identity-platform/
├── kms/
├── load-balancer/
├── monitoring/
├── mqtt-broker/
├── networking/
├── pubsub/
├── secrets/
├── spanner/
└── storage/
3. Backend Configuration
The root configuration deliberately leaves the workspace out of the remote backend block. Select it with the checked-in backend file when running local read-only commands:
terraform {
required_version = ">= 1.7.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 7.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = "~> 7.0"
}
}
backend "remote" {
hostname = "iac-api.peaksuite.dev"
organization = "peakgateway"
}
}
backend-prod.hcl selects gateway; backend-staging.hcl selects
gateway-staging. Terrakube VCS/API runs already know their workspace and do
not use the local selector.
4. Module Specifications
4.1 Project Module
The project module is intentionally empty today. API enablement and core project configuration live in the root main.tf file so Terraform applies them before dependent modules.
# main.tf (API enablement)
resource "google_project_service" "apis" {
for_each = toset([
"run.googleapis.com",
"spanner.googleapis.com",
"pubsub.googleapis.com",
"secretmanager.googleapis.com",
"artifactregistry.googleapis.com",
"compute.googleapis.com",
"vpcaccess.googleapis.com",
"cloudresourcemanager.googleapis.com",
"firebase.googleapis.com",
"identitytoolkit.googleapis.com",
"monitoring.googleapis.com",
"logging.googleapis.com",
"cloudtrace.googleapis.com",
"cloudkms.googleapis.com",
"iam.googleapis.com",
"apphub.googleapis.com",
"dns.googleapis.com",
"storage.googleapis.com",
])
project = var.project_id
service = each.value
disable_on_destroy = false
}
4.2 Networking Module
Implements the VPC, subnets, serverless VPC access connector, and firewall rules. It also configures Private Google Access via Cloud DNS.
# modules/networking/main.tf
resource "google_compute_network" "vpc" {
name = "gateway-vpc"
auto_create_subnetworks = false
project = var.project_id
}
resource "google_compute_subnetwork" "subnet" {
name = "gateway-subnet"
ip_cidr_range = "10.0.0.0/24"
region = var.region
network = google_compute_network.vpc.id
project = var.project_id
}
resource "google_vpc_access_connector" "connector" {
name = "gateway-vpc-connector"
region = var.region
network = google_compute_network.vpc.name
ip_cidr_range = "10.8.0.0/28"
project = var.project_id
}
# Private Google Access DNS and routes are also configured here
4.3 Cloud Run Module
Deploys microservices using for_each. Services are internal-only by default (accessible via Load Balancer or internal VPC). Includes probes and IAM bindings.
# modules/cloud-run/main.tf
resource "google_cloud_run_v2_service" "service" {
for_each = var.services
name = "gateway-${each.key}"
location = var.region
project = var.project_id
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
template {
service_account = var.service_accounts[each.key]
vpc_access {
network_interfaces {
network = var.vpc_network_id
subnetwork = var.vpc_subnet_id
tags = ["cloud-run"]
}
egress = "PRIVATE_RANGES_ONLY"
}
containers {
image = each.value.image
startup_probe {
http_get { path = "/health" }
}
liveness_probe {
http_get { path = "/health" }
}
# Environment variables and secrets mapped dynamically
}
}
}
4.4 Spanner Module
Configures the Cloud Spanner instance and database with CMEK encryption and a daily backup schedule.
# modules/spanner/main.tf
resource "google_spanner_instance" "instance" {
name = "gateway-spanner"
config = "regional-${var.region}"
processing_units = var.spanner_processing_units
project = var.project_id
}
resource "google_spanner_database" "database" {
instance = google_spanner_instance.instance.name
name = "gateway-db"
project = var.project_id
database_dialect = "POSTGRESQL"
encryption_config {
kms_key_name = var.kms_key_id
}
}
resource "google_spanner_backup_schedule" "daily" {
instance = google_spanner_instance.instance.name
database = google_spanner_database.database.name
name = "daily-backup"
spec {
cron_spec { text = "0 2 * * *" }
}
}
5. Environment Configuration
Terrakube workspace variables are the source of truth for live environment values. Both environments use the same unsuffixed resource names; the GCP project separates them.
| Setting | Staging | Production |
|---|---|---|
| Workspace | gateway-staging | gateway |
| GCP project | peakgateway-staging | pinpoint-gateway |
| Cloud Run region | us-east1 | us-east1 |
| API host | staging-api.peakgateway.co | api.peakgateway.co |
| Payment host | staging-pay.peakgateway.co | pay.peakgateway.co |
| Artifact Registry | Pulls cross-project from production | Hosted in pinpoint-gateway |
Checked-in variable defaults are fallbacks, not a record of live workspace
configuration. Never infer a deployed value from default alone; inspect the
target Terrakube workspace and the active GCP resource before an operational
change.
6. Local Read-Only Commands
cd infra/tf/gcp
# Staging
terraform init -reconfigure -backend-config=backend-staging.hcl
terraform plan
# Production: reinitialize before changing backend
terraform init -reconfigure -backend-config=backend-prod.hcl
terraform plan
Apply through the authorized Terrakube workspace. Local production applies are not part of the supported workflow.