What It Is
A Google Cloud landing zone is a secure and scalable foundation for running workloads built as infrastructure as code. It standardizes the resource hierarchy, guardrails, networking, identity, observability, and security controls so teams can ship without reinventing the basics each time. Google’s landing zone guidance defines these domains and shows how they fit together.
Why You Need It
- Consistency at scale so teams reuse proven patterns
- Least privilege by design through organization policies and hierarchical firewalls
- Separation of duties through host projects for networking and service projects for apps
- Auditability through centralized logging and controllable egress
- A path to advanced protection through Private Service Connect, VPC Service Controls, Interconnect, and Network Connectivity Center
AWS → Google Cloud Mental Map
If you’re coming from AWS, like me, think of accounts as projects in Google Cloud—smaller, easier containers for apps and billing. What AWS calls Service Control Policies become Google Cloud organization policies with a top-down firewall, so you set guardrails once at the org or folder and inherit them everywhere. The “central networking account” idea maps to a shared network per environment (Google Cloud’s Shared VPC) that app projects plug into. Security groups/NACLs (Network Access Control List) translate to firewall rules plus those top-level policies, so most rules live centrally. For identity, use your company groups for people and service accounts with short-lived access for automation. And when you need private, internal-only access to services, AWS PrivateLink maps to Google Cloud’s Private Service Connect.
How to Build One with Terraform
Assumptions
You have a Google Cloud Organization and Billing Account. Terraform will authenticate with Workload Identity Federation and service account impersonation so no long-lived keys are required. The snippets below are minimal and version pinning is expected. Variables are not shown for the sake of brevity.
Bootstrap and Authentication
Create a small bootstrap project and a versioned GCS bucket for Terraform state. Use impersonation in the provider block so CI exchanges an OIDC token for short-lived credentials.
terraform {
required_version = ">= 1.6"
required_providers {
google = { source = "hashicorp/google", version = "~> 6.0" }
}
backend "gcs" {
bucket = var.state_bucket
prefix = "tf/landing-zone/bootstrap"
}
}
provider "google" {
project = var.bootstrap_project_id
region = var.default_region
impersonate_service_account = var.deploy_sa_email
}
This aligns with Google guidance to automate foundational setup and govern deployment through org-level identity.
Resource Hierarchy
Start with one Organization. Under it, create four peer folders: Bootstrap, Shared infrastructure, Development environment, and Production environment. Put your Terraform seed/admin projects in Bootstrap. Keep org-wide services (e.g., observability) in Shared infrastructure. In each environment folder, create a Shared VPC host project (for the hub) and attach service projects for workloads. If you need Testing, add another environment folder the same way. This follows Google’s environment-based design and keeps policies, IAM, and billing cleanly separated by environment.
Organization
├── Bootstrap # IaC seed/admin projects
├── Shared infrastructure # org-wide logging/monitoring, etc.
├── Development environment
│ ├── env-shared # e.g., KMS, secrets
│ ├── net-host-development # Shared VPC host project
│ └── app-* # service projects attached to the host
├── Nonproduction environment # optional, if you use a separate “nonprod”
│ ├── env-shared
│ ├── net-host-nonproduction
│ └── app-*
└── Production environment
├── env-shared
├── net-host-production
└── app-*
Guardrails First with Organization Policies
Enforce constraints at the Organization and allow exceptions lower in the tree only when necessary. Use the managed constraint for service account keys and require OS Login. Restrict external VM IPs to drive private access patterns.
resource "google_org_policy_policy" "no_sa_keys" {
name = "organizations/${var.org_id}/policies/constraints/iam.managed.disableServiceAccountKeyCreation"
parent = "organizations/${var.org_id}"
spec { rules { enforce = true } }
}
resource "google_org_policy_policy" "require_oslogin" {
name = "organizations/${var.org_id}/policies/constraints/compute.requireOsLogin"
parent = "organizations/${var.org_id}"
spec { rules { enforce = true } }
}
resource "google_org_policy_policy" "no_external_ips" {
name = "organizations/${var.org_id}/policies/constraints/compute.vmExternalIpAccess"
parent = "organizations/${var.org_id}"
spec { rules { deny_all = true } }
}
These constraints are called out in Google’s landing zone and security foundations guidance.
Networking with Shared VPC per Environment
In Google Cloud a VPC network is global and subnets are regional. Create a Shared VPC in the host project for each environment then attach service projects and delegate minimal roles.
module "vpc_prod" {
source = "terraform-google-modules/network/google"
version = "~> 9.0"
project_id = var.prod_host_project_id
network_name = "vpc-prod"
subnets = [
{ subnet_name = "prod-use1", subnet_ip = "10.10.0.0/20", subnet_region = "us-east1" },
{ subnet_name = "prod-usc1", subnet_ip = "10.10.16.0/20", subnet_region = "us-central1" }
]
}
resource "google_compute_shared_vpc_service_project" "app1_attach" {
host_project = var.prod_host_project_id
service_project = var.app1_project_id
}
resource "google_project_iam_member" "app1_netuser" {
project = var.prod_host_project_id
role = "roles/compute.networkUser"
member = "serviceAccount:${var.app1_ci_sa}"
}
This implements the Shared VPC host and service project model described in the product documentation.
Private Access and Egress Control
Enable Private Google Access on subnets so instances without public IPs can reach Google APIs through private addresses. Provide outbound internet through Cloud NAT with logging.
resource "google_compute_router" "nat_router" {
name = "nat-router-use1"
region = "us-east1"
project = var.prod_host_project_id
network = module.vpc_prod.network_self_link
}
resource "google_compute_router_nat" "nat_use1" {
name = "nat-use1"
project = var.prod_host_project_id
region = google_compute_router.nat_router.region
router = google_compute_router.nat_router.name
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
log_config { enable = true, filter = "ERRORS_ONLY" }
}
Org and Folder Level Firewall Guardrails
Use hierarchical firewall policies for top-down allow and deny rules. Associate the policy at the Organization or at an environment folder and keep VPC local rules minimal.
resource "google_compute_firewall_policy" "org_fw" {
name = "org-default-guardrails"
description = "Deny by default and allow only what is required"
}
resource "google_compute_firewall_policy_rule" "deny_ingress_all" {
firewall_policy = google_compute_firewall_policy.org_fw.name
priority = 1000
direction = "INGRESS"
enable_logging = true
action = "deny"
match { layer4_configs { ip_protocol = "all" } }
}
resource "google_compute_firewall_policy_association" "org_attach" {
name = "org-assoc"
attachment_target = "organizations/${var.org_id}"
firewall_policy = google_compute_firewall_policy.org_fw.name
}
Security Posture to Wire in Early
Enable Security Command Center at the Organization so you have findings across projects. Wrap sensitive Google APIs in VPC Service Controls and define service perimeters with Access Context Manager as your context engine. These services complement IAM to reduce data exfiltration paths.
resource "google_access_context_manager_access_policy" "org" {
parent = "organizations/${var.org_id}"
title = "org-access-policy"
}
resource "google_access_context_manager_access_level" "corp" {
parent = google_access_context_manager_access_policy.org.name
name = "${google_access_context_manager_access_policy.org.name}/accessLevels/corp"
basic {
conditions { ip_subnetworks = var.corp_cidrs }
}
}
resource "google_access_context_manager_service_perimeter" "data" {
parent = google_access_context_manager_access_policy.org.name
name = "${google_access_context_manager_access_policy.org.name}/servicePerimeters/data"
title = "data"
status {
resources = [
"projects/${var.prod_host_project_number}",
"projects/${var.prod_app_project_number}"
]
restricted_services = [
"bigquery.googleapis.com",
"storage.googleapis.com"
]
access_levels = [google_access_context_manager_access_level.corp.name]
vpc_accessible_services {
enable_restriction = true
allowed_services = ["bigquery.googleapis.com","storage.googleapis.com"]
}
}
}
resource "google_pubsub_topic" "scc" {
project = var.siem_project_id
name = "scc-findings"
}
resource "google_scc_v2_organization_notification_config" "scc_pubsub" {
organization = var.org_id
location = var.scc_location
config_id = "pubsub-findings"
pubsub_topic = "projects/${var.siem_project_id}/topics/${google_pubsub_topic.scc.name}"
streaming_config { filter = "state = \\"ACTIVE\\"" }
}
resource "google_bigquery_dataset" "scc" {
project = var.siem_project_id
dataset_id = "scc_findings"
location = "US"
}
resource "google_scc_v2_organization_scc_big_query_export" "scc_bq" {
organization = var.org_id
location = var.scc_location
big_query_export_id = "scc-to-bq"
dataset = google_bigquery_dataset.scc.id
filter = "state = \\"ACTIVE\\""
}
Observability and Logging
Create sinks at Organization or folder scope to export Admin Activity, Data Access, VPC Flow Logs, and Cloud NAT logs to BigQuery or a SIEM. Cloud NAT logging captures translations and errors for analysis in Cloud Logging.
resource "google_bigquery_dataset" "logs" {
project = var.log_project_id
dataset_id = "org_logs"
location = "US"
}
resource "google_logging_organization_sink" "org_to_bq" {
org_id = var.org_id
name = "org-logs-to-bq"
destination = "bigquery.googleapis.com/projects/${var.log_project_id}/datasets/${google_bigquery_dataset.logs.dataset_id}"
include_children = true
filter = "logName:\\"cloudaudit.googleapis.com/activity\\" OR logName:\\"cloudaudit.googleapis.com/data_access\\" OR resource.type=(\\"gce_subnetwork\\" OR \\"gce_router\\")"
}
resource "google_bigquery_dataset_iam_member" "sink_writer" {
project = var.log_project_id
dataset_id = google_bigquery_dataset.logs.dataset_id
role = "roles/bigquery.dataEditor"
member = google_logging_organization_sink.org_to_bq.writer_identity
}
Repository Layout that Scales
landing-zone/
00-bootstrap/
10-org-policies/
20-network-core/
prod-host/
vpc/
firewall-hier/
nonprod-host/
30-project-factory/
40-security/
This layout matches the enterprise foundations blueprints and deployment methodology for repeatable rollouts.
Common Pitfalls
New builders often stumble on IP planning and peering. Pick your IP ranges early and avoid overlaps—Google Cloud’s network is global while subnets are regional. Don’t chain VPC peering to mimic a transit hub; instead, plug projects into the shared network for each environment. Also remember that “no public IP” doesn’t mean “no internet”—instances can still make outbound calls through your managed egress gateway, so turn on logs and control it. Finally, Google APIs are off by default: enable the ones you need in Terraform before creating resources to prevent confusing errors.
Next Steps
Start by sending logs to one place, set budget alerts and quotas, and use temporary credentials for Terraform instead of downloaded keys. Automate project creation and attachment to the shared network so every team starts the same way, every time. When you’re ready, add simple platform pieces like Cloud Run or GKE from blueprints, and only bolt on a VPN to your data center if you actually need it.
Conclusion
A landing zone in Google Cloud is just a safe, ready-made starting point you reuse for every team. Think “starter home” with the basics already wired: a clean folder structure, shared networks, sensible security rules, and logging. You want it because it removes guesswork, speeds up new projects, keeps costs tidy, and bakes in good security from day one—so you don’t rebuild the foundation every time.
To build it, keep it simple. Create four top-level folders: one for your setup tools, one for shared company services like logging and monitoring, and one each for development and production. In both development and production, stand up a single shared network and plug each app project into the right one. Keep servers off the public internet; when they need to call out, send their traffic through a small managed gateway and make sure it’s logged. Set organization-wide rules—no long-lived keys, use company logins for servers, and block public addresses by default—then only carve out exceptions when you truly need them. Run your infrastructure as code with temporary credentials so there are no secrets sitting around. Start with that foundation; when it’s steady, add the extras you actually need, like central security findings, tighter data boundaries, or private connections back to your data center. That’s your landing zone: clear, secure, and easy to grow.