GCP Infrastructure Overview
The platform is organized into several project layers — bootstrap, organization, platform, shared images, and tenant — provisioned by a corresponding set of numbered Terraform stacks, with a single shared GKE cluster per environment and namespace-based isolation between applications.
Project layers
Bootstrap — u2i-bootstrap (us-central1)
Terraform state, Cloud Deploy pipelines, GitHub connections, and the shared Artifact Registry live here. Region chosen for cost, not data residency.
Organization — u2i-dns, u2i-security, u2i-logging
DNS zones, security tooling, and centralized audit logging (7-year retention). Note: the org's Google Workspace groups are managed by Terraform too, but that stack (foundation/1-organization) only creates Cloud Identity groups — it doesn't own a GCP project of its own.
Platform — u2i-platform-nonprod / u2i-platform-prod (europe-west1)
Fleet-host projects: fleet membership, Connect Gateway, Config Sync, and Binary Authorization for the region.
Shared images — shared-images-nonprod (europe-west1)
Hosts base/build images (build-lib, meteor, mock-idp, ...) every app's Cloud Build pipeline pulls from. One project serves both nonprod and prod builds — there's no shared-images-prod.
Tenant — c-u2i-nonprod / c-u2i-prod (europe-west1)
The shared tenant project that hosts every application's namespace, images, and Cloud Build/Deploy resources. europe-west1 is used here for GDPR/EU data residency.
Foundation Terraform stacks
The numbered folders under foundation/
0-bootstrap, 1-organization, 2-security, 3-platform, 4-tenants, 5-shared-images, 6-dns. The numbers are roughly apply order, driven by which stack's Terraform state each one reads: 2-security and 3-platform each read 1-organization; 4-tenants reads 0-bootstrap, 1-organization AND 2-security (the heaviest dependency, since registering an app's IAM needs both org structure and security tooling in place); 5-shared-images reads only 1-organization.
6-dns is the odd one out
It doesn't read any other stack's Terraform state at all — instead it looks up each app's DNS subzone live via data "google_dns_managed_zone", which only resolves once that subzone actually exists in GCP. So in practice 6-dns has to be applied after 4-tenants (which creates the subzone) even though nothing in Terraform's own dependency graph enforces that order — the numbering suggests a dependency that isn't actually there. This bit us directly while onboarding an app: the tenant registration (4-tenants) had to be merged and applied before the DNS delegation (6-dns) PR would even plan successfully.
GKE clusters
Shared Autopilot clusters
One cluster per environment (u2i-nonprod, u2i-prod) in europe-west1. Every app is a namespace on the shared cluster rather than getting its own cluster.
Namespace isolation
Namespaces follow {app}-dev, {app}-qa, {app}-prod, and {app}-pr-{N} for pull-request previews.
Networking
Private nodes behind Cloud NAT; dual-stack IPv4/IPv6 in nonprod, IPv4 only in prod. Pod/service CIDRs are allocated centrally so they stay globally unique across clusters.
IAM & service accounts
Zero standing privilege
Terraform service accounts are read-only by default. Write access is granted only through PAM: 30-minute windows for CI/CD, 2-hour windows for break-glass access.
Per-app service accounts
Each app gets a {app}-ci account (builds images, creates Cloud Deploy releases) and a {app}-deploy-sa account — the one actually set as the Cloud Deploy target's execution_configs.service_account, so it's what runs RENDER/DEPLOY/POSTDEPLOY against the cluster.
No standing keys
Everything authenticates via Application Default Credentials locally or Workload Identity Federation in CI — no long-lived service account keys.
Compliance & security
terraform-google-compliance-modules
Reusable modules aligned with ISO 27001 and SOC 2 Type II. Google-managed encryption is accepted for GKE/Compute; CMEK is still required for databases, BigQuery, and Secret Manager.
Centralized logging
All audit logs flow into the u2i-security project with a 7-year retention policy.
Privileged Access Manager (PAM)
Time-bound elevation with an audit trail written to BigQuery and a Slack notification on every grant.
Storage: GCS via GCSFuse CSI
Native CSI driver, no sidecars
Apps that need bucket-backed storage (e.g. RetroTool's board backgrounds) mount GCS through GKE's gcsfuse.csi.storage.gke.io driver directly on the pod — not a sidecar container.
Tunable per environment
Cache sizes (fileCacheCapacity, ephemeral storage requests/limits) are set per environment in Helm values — smaller in preview to fit spot instances.
Bucket provisioning
Buckets are provisioned declaratively via Config Connector through the gke-tenant-storage chart; the app's GKE service account gets roles/storage.objectUser via Workload Identity.