Home โ€บ DevOps โ€บ GCP Shared VPC: Centralized Network Administration for Multi-Project
Advanced 8 min · July 12, 2026

GCP Shared VPC: Centralized Network Administration for Multi-Project

Master GCP Shared VPC for centralized multi-project networking: host/service project setup, subnet-level IAM, firewall policies, GKE integration, Cloud NAT, and migration patterns..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 30 min
  • Google Cloud Platform account with Organization, gcloud CLI (>= 400.0.0), Terraform (>= 1.3), basic understanding of VPC networking, IAM, and GKE
โœฆ Definition~90s read
What is Shared VPC?

GCP Shared VPC lets you centrally manage a single Virtual Private Cloud network across multiple Google Cloud projects, enabling consistent policy enforcement, simplified routing, and reduced administrative overhead. It matters because it decouples network ownership from application ownership, allowing platform teams to control network security while giving service teams autonomy over their compute resources.

โ˜…
GCP Shared VPC: Centralized Network Administration for Multi-Project is like having a specialized tool that handles shared vpc so you don't have to build and manage it yourself โ€” it just works out of the box with Google Cloud's infrastructure.

Use it when you need to scale multi-project architectures with strict compliance, centralized egress, or shared services like Cloud NAT and Private Google Access.

Plain-English First

GCP Shared VPC: Centralized Network Administration for Multi-Project is like having a specialized tool that handles shared vpc so you don't have to build and manage it yourself โ€” it just works out of the box with Google Cloud's infrastructure.

You deployed a new microservice in a separate project, and suddenly your production database is unreachable. The network team blames the app team, the app team blames IAM, and three hours later you discover someone deleted a VPC peering connection. This is the reality of managing networks without Shared VPC. GCP Shared VPC flips the model: one network, many projects, central control. It eliminates the complexity of VPC peering meshes, reduces attack surface by enforcing consistent firewall rules, and gives you a single pane of glass for audit logs. If you're running more than three projects with any inter-project communication, you're wasting time and risking outages without it. This article walks you through the architecture, implementation, and operational pitfalls of Shared VPC โ€” from a production engineer who's cleaned up the mess when it's done wrong.

The Shared VPC Architecture: Host vs. Service Projects

Shared VPC splits responsibility into two project types: the host project owns the VPC network, subnets, firewall rules, and routes. Service projects attach to the host and consume its subnets for their resources (GKE, Compute Engine, Cloud SQL, etc.). This separation is critical: the host project is managed by a central network team, while service project owners deploy workloads without touching network configuration. The host project can have multiple VPCs, but each service project attaches to exactly one host project. Resources in service projects get IPs from the host project's subnets, and all traffic flows through the host project's firewall rules. This means you can enforce egress via Cloud NAT in the host project, block SSH from the internet globally, and audit all traffic with VPC Flow Logs from a single project. The key constraint: service projects cannot create their own subnets or modify host project firewall rules. This is a feature, not a bug โ€” it prevents the sprawl that plagues multi-project setups.

enable-shared-vpc.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# Enable Shared VPC for host project
HOST_PROJECT_ID="network-host-prod"
SERVICE_PROJECT_ID="app-service-prod"

gcloud compute shared-vpc enable "$HOST_PROJECT_ID"

# Attach service project to host
gcloud compute shared-vpc associated-projects add "$SERVICE_PROJECT_ID" \
    --host-project "$HOST_PROJECT_ID"

# Grant service project network user role to a service account
gcloud projects add-iam-policy-binding "$SERVICE_PROJECT_ID" \
    --member="serviceAccount:app-sa@$SERVICE_PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/compute.networkUser" \
    --condition="expression=resource.name.startsWith('projects/$HOST_PROJECT_ID/regions/us-central1/subnetworks/app-subnet'),title=restrict_to_app_subnet"
Output
Shared VPC enabled for [network-host-prod].
Associated project [app-service-prod] added.
Updated IAM policy for project [app-service-prod].
โš  IAM Condition Gotcha
Without IAM conditions, granting compute.networkUser gives access to ALL subnets in the host project. Always scope to specific subnets using resource.name conditions to prevent accidental cross-environment access.
๐Ÿ“Š Production Insight
In production, we saw a team attach a staging service project to a production host project because the host project name was ambiguous. Use naming conventions like 'prod-network-host' and 'staging-network-host' to avoid cross-environment leaks.
๐ŸŽฏ Key Takeaway
Shared VPC separates network ownership from workload ownership via host and service projects.
gcp-shared-vpc THECODEFORGE.IO Shared VPC Provisioning Flow Steps to set up a Shared VPC with host and service projects Create Host Project Designate a project as the Shared VPC host Enable Shared VPC API Activate the Shared VPC feature in host project Define Subnets Carve out IP ranges for service projects Assign Network User Role Grant IAM roles to service project admins Attach Service Projects Link service projects to the host project Deploy Resources Launch VMs, GKE, or other services in subnets โš  Overlapping subnet ranges cause routing conflicts Use non-overlapping CIDR blocks across all projects THECODEFORGE.IO
thecodeforge.io
Gcp Shared Vpc

Subnet Design: Carving Out Space for Scale

Subnet planning in Shared VPC is a one-time decision that haunts you forever if done wrong. Each subnet belongs to the host project and is assigned to a region. Service projects can use any subnet they have IAM access to. The golden rule: use non-overlapping CIDR ranges per environment and per region. For production, reserve /16 per region (e.g., 10.0.0.0/16 for us-central1, 10.1.0.0/16 for us-east1). For staging, use a different prefix like 10.64.0.0/16. Never use the same CIDR across environments โ€” it breaks peering and migration. Secondary ranges are essential for GKE pods and services: allocate /16 for pods and /20 for services per cluster. Use a consistent naming scheme: 'subnet-prod-us-central1-app', 'subnet-prod-us-central1-gke-pods'. Avoid creating subnets larger than /16; you'll waste IPs and hit routing table limits. Also, plan for future regions: leave gaps in your IP space (e.g., skip 10.2.0.0/16 for future us-west1).

subnets.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
resource "google_compute_subnetwork" "subnets" {
  for_each = {
    prod-us-central1-app = {
      region    = "us-central1"
      cidr      = "10.0.0.0/20"
      secondary = {
        pods     = "10.0.16.0/20"
        services = "10.0.32.0/24"
      }
    }
    prod-us-east1-app = {
      region    = "us-east1"
      cidr      = "10.1.0.0/20"
      secondary = {
        pods     = "10.1.16.0/20"
        services = "10.1.32.0/24"
      }
    }
  }
  name          = "subnet-${each.key}"
  network       = google_compute_network.vpc.id
  region        = each.value.region
  ip_cidr_range = each.value.cidr
  secondary_ip_range {
    range_name    = "${each.key}-pods"
    ip_cidr_range = each.value.secondary.pods
  }
  secondary_ip_range {
    range_name    = "${each.key}-services"
    ip_cidr_range = each.value.secondary.services
  }
}
Output
google_compute_subnetwork.subnets["prod-us-central1-app"]: Creation complete after 12s
google_compute_subnetwork.subnets["prod-us-east1-app"]: Creation complete after 11s
๐Ÿ’กSecondary Ranges Are Not Optional
GKE requires secondary ranges for pods and services. If you skip them, you'll be forced to use VPC-native clusters with auto-allocated ranges, which can conflict with existing routes. Always define them upfront.
๐Ÿ“Š Production Insight
We once allocated a /24 for GKE pods in a /16 subnet. After 50 clusters, we ran out of pod IPs. Use at least /20 per cluster for pods, and monitor utilization with VPC IP space alerts.
๐ŸŽฏ Key Takeaway
Plan subnet CIDRs with non-overlapping ranges per environment and region, and always include secondary ranges for GKE.

IAM and Access Control: The Network User Role

The compute.networkUser role is the linchpin of Shared VPC security. It grants a service project's identities (users, service accounts, groups) permission to use subnets in the host project. Without it, resources in the service project cannot create network interfaces. The role must be granted at the host project level, but you should scope it with IAM conditions to specific subnets. For example, a staging service project should only access staging subnets. Use groups for role assignment: create a group like 'gcp-network-users-prod' and add service accounts to it. This avoids managing individual bindings. Also, grant compute.networkUser to the GKE service agent (service-PROJECT_NUMBER@container-engine-robot.iam.gserviceaccount.com) so GKE clusters can create nodes in the host project's subnets. Never grant compute.networkAdmin to service project users โ€” that allows them to modify firewall rules and routes, defeating the purpose of centralization.

iam-network-user.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
HOST_PROJECT="network-host-prod"
SERVICE_PROJECT="app-service-prod"
SUBNET="projects/$HOST_PROJECT/regions/us-central1/subnetworks/subnet-prod-us-central1-app"

# Grant networkUser to a group
gcloud projects add-iam-policy-binding "$HOST_PROJECT" \
    --member="group:gcp-network-users-prod@example.com" \
    --role="roles/compute.networkUser" \
    --condition="expression=resource.name.startsWith('$SUBNET'),title=restrict_to_app_subnet"

# Grant networkUser to GKE service agent
GKE_SA="service-$(gcloud projects describe $SERVICE_PROJECT --format='value(projectNumber)')@container-engine-robot.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding "$HOST_PROJECT" \
    --member="serviceAccount:$GKE_SA" \
    --role="roles/compute.networkUser" \
    --condition="expression=resource.name.startsWith('$SUBNET'),title=gke_subnet_access"
Output
Updated IAM policy for project [network-host-prod].
Updated IAM policy for project [network-host-prod].
โš  GKE Service Agent Needs Explicit Access
If you forget to grant compute.networkUser to the GKE service agent, cluster creation will fail with 'permission denied' on the subnet. Always add it before creating clusters.
๐Ÿ“Š Production Insight
We had a production outage because a new team member granted compute.networkAdmin to a service project. They accidentally deleted a firewall rule, exposing a Cloud SQL instance to the internet. Audit IAM bindings weekly.
๐ŸŽฏ Key Takeaway
Use IAM conditions to scope compute.networkUser to specific subnets, and always grant it to GKE service agents.
gcp-shared-vpc THECODEFORGE.IO Shared VPC Architecture Layers Hierarchical components from host project to service resources Host Project Shared VPC Network | Subnets | Firewall Rules IAM & Access Control Network Admin Role | Network User Role | Service Account Bindings Service Projects Project A | Project B | Project C Workloads Compute Engine | GKE Clusters | Cloud Functions Connectivity VPC Peering | Hybrid Connectivity | Private Google Access THECODEFORGE.IO
thecodeforge.io
Gcp Shared Vpc

Firewall Rules: Centralized Enforcement with Service Project Exceptions

Firewall rules in Shared VPC are defined in the host project and apply to all resources in attached service projects. This is powerful: you can block all inbound SSH from the internet with a single rule. However, service projects sometimes need their own rules (e.g., allow health checks from a specific range). You have two options: create global rules in the host project, or use service project firewall rules (which are allowed but discouraged). The best practice is to centralize all ingress rules in the host project and use network tags for fine-grained control. For example, tag all GKE nodes with 'gke-node' and create a rule allowing port 10250 from monitoring ranges. Avoid service project firewall rules because they create a fragmented policy that's hard to audit. Use hierarchical firewall policies for organization-level rules that apply across all host projects. Also, enable VPC Flow Logs on subnets to capture denied traffic โ€” it's invaluable for debugging.

firewall.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
resource "google_compute_firewall" "allow-health-checks" {
  name    = "fw-allow-health-checks"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["80", "443"]
  }

  source_ranges = ["35.191.0.0/16", "130.211.0.0/22"]
  target_tags   = ["http-server"]
}

resource "google_compute_firewall" "deny-all-ssh" {
  name    = "fw-deny-all-ssh"
  network = google_compute_network.vpc.name

  deny {
    protocol = "tcp"
    ports    = ["22"]
  }

  source_ranges = ["0.0.0.0/0"]
  priority      = 1000
}

resource "google_compute_firewall" "allow-ssh-from-bastion" {
  name    = "fw-allow-ssh-from-bastion"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  source_ranges = ["10.0.100.0/24"]
  target_tags   = ["bastion-accessible"]
  priority      = 900
}
Output
google_compute_firewall.fw-allow-health-checks: Creation complete after 8s
google_compute_firewall.fw-deny-all-ssh: Creation complete after 7s
google_compute_firewall.fw-allow-ssh-from-bastion: Creation complete after 7s
๐Ÿ”ฅFirewall Priority Matters
GCP firewall rules have a priority from 0 (highest) to 65535 (lowest). Default rules have priority 65535. Always set explicit priorities for deny rules to ensure they override allows. Use priority 1000 for denies and 900 for specific allows.
๐Ÿ“Š Production Insight
A team created a service project firewall rule allowing all traffic from 0.0.0.0/0 to port 3306, thinking it only applied to their project. It didn't โ€” it applied to all resources in the host project's VPC. We caught it with a VPC Flow Log alert on denied traffic. Always audit service project firewall rules.
๐ŸŽฏ Key Takeaway
Centralize firewall rules in the host project and use network tags for granularity; avoid service project firewall rules.

Private Google Access and Cloud NAT: Centralized Egress

Shared VPC enables centralized egress through Cloud NAT and Private Google Access. Configure Cloud NAT in the host project on each subnet that needs outbound internet access. Service project resources automatically use this NAT โ€” no per-project configuration needed. This is critical for compliance: all outbound traffic goes through a single set of IPs, simplifying allowlisting with third parties. Private Google Access allows VMs without external IPs to reach Google APIs (e.g., Cloud Storage, BigQuery) over Google's internal network. Enable it per subnet in the host project. The combination means service project VMs can have zero public IPs, reducing attack surface. However, beware of NAT gateway limits: each Cloud NAT gateway supports up to 64,000 concurrent connections per VM. For high-throughput workloads, use multiple NAT IPs or consider a third-party NVA. Also, monitor NAT gateway utilization with Cloud Monitoring to avoid connection drops.

cloud-nat.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
resource "google_compute_router" "router" {
  name    = "router-prod-us-central1"
  network = google_compute_network.vpc.name
  region  = "us-central1"
}

resource "google_compute_router_nat" "nat" {
  name                               = "nat-prod-us-central1"
  router                             = google_compute_router.router.name
  region                             = "us-central1"
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"

  subnetwork {
    name                    = google_compute_subnetwork.subnets["prod-us-central1-app"].id
    source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
  }

  # Enable logging for audit
  log_config {
    enable = true
    filter = "ERRORS_ONLY"
  }
}

# Enable Private Google Access on subnet
resource "google_compute_subnetwork" "subnet_with_pga" {
  name                     = "subnet-prod-us-central1-app"
  network                  = google_compute_network.vpc.id
  region                   = "us-central1"
  ip_cidr_range            = "10.0.0.0/20"
  private_ip_google_access = true
}
Output
google_compute_router.router: Creation complete after 10s
google_compute_router_nat.nat: Creation complete after 15s
google_compute_subnetwork.subnet_with_pga: Creation complete after 12s
๐Ÿ’กNAT Logging Is Cheap Insurance
Enable NAT logging with ERRORS_ONLY filter to capture dropped connections. It's low volume and helps debug egress failures without drowning in data.
๐Ÿ“Š Production Insight
We had a batch job that opened 100,000 connections to an external API. Cloud NAT's 64,000 connection limit per VM caused random failures. We switched to static NAT IPs and spread the workload across multiple VMs. Monitor connection counts with Cloud Monitoring.
๐ŸŽฏ Key Takeaway
Centralize egress with Cloud NAT and Private Google Access in the host project to eliminate public IPs on VMs.

VPC Peering and Hybrid Connectivity: Extending the Shared VPC

Shared VPC networks can peer with other VPCs (including on-prem via Cloud VPN or Dedicated Interconnect). This allows you to extend the centralized network model to hybrid environments. For example, peer the host project's VPC with a on-prem network via Cloud VPN, and all service projects automatically get connectivity to on-prem resources. However, peering is not transitive: if VPC A is peered with VPC B, and VPC B is peered with VPC C, VPC A does not automatically reach VPC C. You must set up direct peering between A and C. Also, beware of overlapping CIDRs โ€” they break peering. Use non-overlapping ranges from the start. For hybrid connectivity, use a dedicated project for VPN/Interconnect and peer it with the host project. This keeps the network team in control of the VPN tunnel while service projects remain unaware. Use Cloud Router with dynamic routing (BGP) to propagate routes automatically.

vpc-peering.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
resource "google_compute_network_peering" "peer-with-onprem" {
  name         = "peer-host-to-onprem"
  network      = google_compute_network.vpc.id
  peer_network = "projects/onprem-project/global/networks/onprem-vpc"

  export_custom_routes = true
  import_custom_routes = true
}

# Cloud Router for dynamic routing
resource "google_compute_router" "hybrid-router" {
  name    = "router-hybrid"
  network = google_compute_network.vpc.name
  region  = "us-central1"
  bgp {
    asn = 64514
  }
}

# VPN tunnel (simplified)
resource "google_compute_vpn_tunnel" "tunnel" {
  name          = "tunnel-to-onprem"
  peer_ip       = "203.0.113.1"
  shared_secret = var.vpn_secret
  router        = google_compute_router.hybrid-router.id
  vpn_gateway   = google_compute_vpn_gateway.gateway.id
}
Output
google_compute_network_peering.peer-with-onprem: Creation complete after 20s
google_compute_router.hybrid-router: Creation complete after 10s
google_compute_vpn_tunnel.tunnel: Creation complete after 30s
โš  Peering Route Limits
Each VPC can have up to 100 dynamic routes from Cloud Router. If you have many on-prem subnets, aggregate them into larger CIDRs to stay under the limit.
๐Ÿ“Š Production Insight
We peered a Shared VPC with an on-prem network that had overlapping CIDR (10.0.0.0/8). Traffic to on-prem was blackholed. We had to re-IP the on-prem network โ€” a six-month project. Always verify no overlap before peering.
๐ŸŽฏ Key Takeaway
Peer the host VPC with on-prem networks for hybrid connectivity; peering is not transitive, so plan accordingly.

GKE in Shared VPC: Cluster Configuration and Node Pools

Deploying GKE clusters in a Shared VPC requires careful configuration. The cluster's VPC-native mode must use the host project's subnets. Specify the subnet for nodes, and secondary ranges for pods and services. The GKE service agent needs compute.networkUser on the subnet. Node pools can use different subnets, but all must be in the same region as the cluster. Use separate node pools for system and user workloads, each with its own subnet if needed. Enable Dataplane V2 for better network policy enforcement. Also, configure master authorized networks to restrict access to the control plane. A common pitfall: forgetting to grant the GKE service agent access to the subnet's secondary ranges. The error message is cryptic: 'failed to create cluster: googleapi: Error 400: Invalid value for field 'resource.clusterIpv4Cidr' โ€” but the real issue is IAM. Always test cluster creation with a dry run.

create-gke-cluster.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
HOST_PROJECT="network-host-prod"
SERVICE_PROJECT="app-service-prod"
CLUSTER_NAME="app-cluster"
REGION="us-central1"
SUBNET="projects/$HOST_PROJECT/regions/$REGION/subnetworks/subnet-prod-us-central1-app"
POD_RANGE="subnet-prod-us-central1-app-pods"
SERVICE_RANGE="subnet-prod-us-central1-app-services"

gcloud container clusters create "$CLUSTER_NAME" \
    --project "$SERVICE_PROJECT" \
    --region "$REGION" \
    --network "projects/$HOST_PROJECT/global/networks/vpc-prod" \
    --subnetwork "$SUBNET" \
    --cluster-secondary-range-name "$POD_RANGE" \
    --services-secondary-range-name "$SERVICE_RANGE" \
    --enable-ip-alias \
    --enable-dataplane-v2 \
    --master-authorized-networks "10.0.100.0/24" \
    --enable-master-authorized-networks
Output
Creating cluster app-cluster in us-central1... Cluster is being health-checked... done.
๐Ÿ”ฅDry Run Before Creating
Use --dry-run flag to validate cluster creation without actually creating it. This catches IAM and subnet issues early.
๐Ÿ“Š Production Insight
We had a cluster that failed to create because the secondary range name was misspelled. The error said 'subnet not found' but the real issue was a typo. Use Terraform to avoid manual errors.
๐ŸŽฏ Key Takeaway
GKE in Shared VPC requires explicit subnet and secondary range references, plus IAM for the GKE service agent.

Monitoring and Auditing: VPC Flow Logs and Network Intelligence Center

Visibility is non-negotiable in Shared VPC. Enable VPC Flow Logs on all subnets in the host project to capture traffic metadata. Sample rate of 1 in 10 is sufficient for most environments; increase to 1 in 2 for critical subnets. Export logs to BigQuery for analysis โ€” you can detect anomalous traffic, identify unused IPs, and troubleshoot connectivity. Use Network Intelligence Center (NIC) for topology visualization and connectivity tests. NIC can simulate traffic between any two endpoints and show the path, firewall rules applied, and any drops. This is invaluable for debugging 'why can't VM A talk to VM B' in a multi-project setup. Also, set up alerts for high NAT connection counts, dropped packets, and firewall rule changes. Use Cloud Monitoring dashboards to track subnet utilization and alert when IP space is below 20%.

flow-logs.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
resource "google_compute_subnetwork" "subnet_with_logs" {
  name             = "subnet-prod-us-central1-app"
  network          = google_compute_network.vpc.id
  region           = "us-central1"
  ip_cidr_range    = "10.0.0.0/20"
  log_config {
    aggregation_interval = "INTERVAL_5_SEC"
    flow_sampling        = 0.1
    metadata             = "INCLUDE_ALL_METADATA"
  }
}

# BigQuery export sink
export "google_logging_project_sink" "flow_logs_bq" {
  name        = "flow-logs-to-bq"
  destination = "bigquery.googleapis.com/projects/${var.log_project}/datasets/vpc_flow_logs"
  filter      = "resource.type=\"gce_subnetwork\" AND logName=\"projects/${var.host_project}/logs/compute.googleapis.com%2Fvpc_flows\""
}
Output
google_compute_subnetwork.subnet_with_logs: Creation complete after 12s
google_logging_project_sink.flow_logs_bq: Creation complete after 5s
๐Ÿ’กFlow Logs Cost Money โ€” Sample Wisely
At 0.1 sampling, a busy subnet can generate hundreds of GB per day. Set a budget alert on BigQuery costs. Use INTERVAL_5_SEC for high-fidelity or INTERVAL_1_MIN for cost savings.
๐Ÿ“Š Production Insight
We discovered a crypto miner in a service project by analyzing VPC Flow Logs for traffic to known mining pools. The logs showed outbound connections to a suspicious IP. We shut it down within an hour.
๐ŸŽฏ Key Takeaway
Enable VPC Flow Logs and use Network Intelligence Center for visibility; export logs to BigQuery for analysis.

Organizational Policy and Hierarchy: Enforcing Shared VPC at Scale

At the organization level, you can enforce Shared VPC usage via organizational policies. Use the compute.restrictSharedVpcHostProject constraint to limit which projects can be host projects. This prevents rogue teams from creating their own Shared VPCs. Also, use compute.restrictSharedVpcSubnetworks to restrict which subnets service projects can use. Combine with IAM conditions for defense in depth. For large organizations, use folder-level host projects: each folder (e.g., 'Production', 'Staging') has its own host project. This isolates environments. Use Terraform to manage the hierarchy: create host projects per folder, attach service projects, and apply firewall rules. Automate with CI/CD to prevent drift. Also, set up a centralized network CI/CD pipeline that deploys firewall rules and subnets from a single repository. This ensures all changes are reviewed and audited.

org-policy.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
resource "google_organization_policy" "restrict_host_projects" {
  org_id     = var.org_id
  constraint = "compute.restrictSharedVpcHostProject"
  list_policy {
    allow {
      values = ["projects/network-host-prod", "projects/network-host-staging"]
    }
  }
}

resource "google_folder_organization_policy" "restrict_subnets" {
  folder     = google_folder.production.name
  constraint = "compute.restrictSharedVpcSubnetworks"
  list_policy {
    allow {
      values = ["projects/network-host-prod/regions/us-central1/subnetworks/subnet-prod-*"]
    }
  }
}
Output
google_organization_policy.restrict_host_projects: Creation complete after 15s
google_folder_organization_policy.restrict_subnets: Creation complete after 12s
โš  Org Policies Are Slow to Propagate
Changes to organizational policies can take up to 24 hours to propagate. Plan ahead and test in a non-production folder first.
๐Ÿ“Š Production Insight
We had a team create a host project in the 'Development' folder that used the same CIDR as production. Org policies prevented it, but only after we added the constraint. Implement policies before onboarding teams.
๐ŸŽฏ Key Takeaway
Use organizational policies to restrict which projects can be host projects and which subnets service projects can use.

Migration from VPC Peering to Shared VPC

Migrating from a VPC peering mesh to Shared VPC is painful but necessary for scale. The key challenge: you cannot change the network of existing resources. You must recreate them in the Shared VPC. Strategy: create the Shared VPC host project and subnets, then gradually migrate workloads. For GCE instances, use instance templates with the new subnet and rolling update. For GKE, create a new cluster in the Shared VPC and migrate workloads using a blue-green deployment. For Cloud SQL, you must restore a backup into the new VPC. Use a migration project as a staging ground: attach it to the host project, deploy new resources, and cut over traffic. During migration, maintain peering between old and new VPCs for connectivity. Once all workloads are migrated, delete the old VPCs and peering connections. This process can take weeks โ€” plan for downtime windows.

migrate-instance.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# Create new instance in Shared VPC
OLD_INSTANCE="web-server-old"
NEW_INSTANCE="web-server-new"
ZONE="us-central1-a"
SUBNET="projects/network-host-prod/regions/us-central1/subnetworks/subnet-prod-us-central1-app"

gcloud compute instances create "$NEW_INSTANCE" \
    --zone "$ZONE" \
    --subnet "$SUBNET" \
    --source-instance-template "web-server-template" \
    --no-address

# After verifying new instance, delete old one
gcloud compute instances delete "$OLD_INSTANCE" --zone "$ZONE" --quiet
Output
Created instance [web-server-new] in zone [us-central1-a].
Deleted instance [web-server-old].
๐Ÿ”ฅUse Managed Instance Groups for Zero-Downtime Migration
For stateless workloads, use MIGs with rolling update to switch to new instances in the Shared VPC. Set the update type to proactive and maxSurge to 1.
๐Ÿ“Š Production Insight
We migrated a 200-node GKE cluster by creating a new cluster in Shared VPC and using a service mesh to shift traffic gradually. The migration took two weeks but had zero downtime. Plan for double resource costs during migration.
๐ŸŽฏ Key Takeaway
Migration to Shared VPC requires recreating resources; use blue-green deployments and maintain peering during transition.

Common Pitfalls and How to Avoid Them

Even experienced teams hit these Shared VPC landmines. First: overlapping CIDRs. Always use a central IPAM tool to track allocations. Second: forgetting IAM conditions. Without them, a staging service account can accidentally use production subnets. Third: ignoring GKE service agent IAM. Cluster creation fails silently. Fourth: not planning for secondary ranges. You can't add them later without recreating the subnet. Fifth: assuming peering is transitive. It's not โ€” you need explicit peering for each pair. Sixth: not enabling VPC Flow Logs from day one. You'll have no baseline for anomaly detection. Seventh: allowing service project firewall rules. They create a security blind spot. Eighth: not using organizational policies. Without them, teams can create their own host projects, defeating centralization. Mitigate each with automation: use Terraform to enforce standards, run policy-as-code checks in CI/CD, and conduct regular audits.

check-overlap.shHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# Check for CIDR overlap between two projects
PROJECT_A="prod-network-host"
PROJECT_B="staging-network-host"

gcloud compute networks subnets list --project $PROJECT_A --format='value(ipCidrRange)' | sort > /tmp/subnets_a.txt
gcloud compute networks subnets list --project $PROJECT_B --format='value(ipCidrRange)' | sort > /tmp/subnets_b.txt

# Use grep to find overlapping ranges (simplified)
while read cidr; do
  if grep -q "$cidr" /tmp/subnets_b.txt; then
    echo "Overlap found: $cidr"
  fi
done < /tmp/subnets_a.txt
Output
Overlap found: 10.0.0.0/16
โš  IPAM Is Not Optional
Use a spreadsheet or a tool like phpIPAM to track CIDR allocations. Without it, you will have overlaps. We learned this the hard way after a three-hour outage.
๐Ÿ“Š Production Insight
A team created a subnet with the same CIDR as an existing on-prem network. Traffic to on-prem was silently dropped. We now run a CIDR overlap check in every Terraform plan.
๐ŸŽฏ Key Takeaway
Avoid common pitfalls by using IAM conditions, planning secondary ranges, and enforcing organizational policies.

Automation with Terraform and CI/CD

Manual Shared VPC management doesn't scale. Use Terraform to define the entire network as code. Store state in a remote backend with locking. Organize modules: one for host project, one for subnets, one for firewall rules, one for IAM. Use workspaces or folders for environments. CI/CD pipeline: run terraform plan on every PR, require approval for production changes, and apply automatically after merge. Use Terratest or OPA for policy-as-code: enforce that all subnets have flow logs enabled, no public IPs on VMs, and firewall rules are tagged. Also, use Google Cloud Build or Terraform Cloud for execution. This ensures consistency and auditability. A common mistake: using terraform import for existing resources without updating state. Always run terraform plan after import to detect drift.

main.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module "host_project" {
  source = "./modules/host-project"
  project_id = "network-host-prod"
  org_id     = var.org_id
}

module "subnets" {
  source   = "./modules/subnets"
  network  = module.host_project.network_self_link
  subnets  = var.subnets
}

module "firewall" {
  source  = "./modules/firewall"
  network = module.host_project.network_name
  rules   = var.firewall_rules
}

module "iam" {
  source      = "./modules/iam"
  host_project = module.host_project.project_id
  service_projects = var.service_projects
}

# CI/CD pipeline config (simplified)
resource "google_cloudbuild_trigger" "network-cicd" {
  trigger_template {
    branch_name = "main"
    repo_name   = "gcp-network"
  }
  substitutions = {
    _ENV = "prod"
  }
  filename = "cloudbuild.yaml"
}
Output
module.host_project: Creation complete after 20s
module.subnets: Creation complete after 15s
module.firewall: Creation complete after 10s
module.iam: Creation complete after 5s
google_cloudbuild_trigger.network-cicd: Creation complete after 8s
๐Ÿ’กUse Terraform Import for Existing Networks
If you have an existing Shared VPC, use terraform import to bring it under management. But be careful: import doesn't generate code โ€” you must write the configuration manually.
๐Ÿ“Š Production Insight
We had a production outage because someone manually added a firewall rule via the console that conflicted with Terraform. Now we use a CI/CD pipeline that reverts any manual changes within 5 minutes.
๐ŸŽฏ Key Takeaway
Automate Shared VPC with Terraform and CI/CD to enforce consistency and auditability.

Subnet-Level IAM vs Project-Level: Least Privilege for Shared VPC

When granting compute.networkUser to service project teams, you have two approaches: project-level or subnet-level IAM. Project-level grants access to ALL subnets in the host project, including future ones. This is simpler but violates least privilegeโ€”a staging team could accidentally use production subnets. Subnet-level IAM binds the role directly on individual subnets, scoping access precisely. To do this, use gcloud compute networks subnets add-iam-policy-binding instead of gcloud projects add-iam-policy-binding. The trade-off: subnet-level requires more management but provides blast radius containment. For organizations with more than 5 service projects, manage subnet IAM with Terraform to avoid drift. A common pattern: create Google Groups per team (e.g., gcp-network-web-team@), grant subnet-level networkUser to the group, and manage group membership in your identity provider. Never use set-iam-policy on subnetsโ€”it replaces the entire policy and can accidentally revoke other teams' access. Always use add-iam-policy-binding.

subnet-iam.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
resource "google_compute_subnetwork_iam_binding" "web_team" {
  project    = "network-host-prod"
  region     = "us-central1"
  subnetwork = "subnet-prod-web"
  role       = "roles/compute.networkUser"

  members = [
    "group:gcp-network-web-team@example.com",
    "serviceAccount:service-PROJECT_NUMBER@container-engine-robot.iam.gserviceaccount.com",
  ]
}

resource "google_compute_subnetwork_iam_binding" "data_team" {
  project    = "network-host-prod"
  region     = "us-east1"
  subnetwork = "subnet-prod-data"
  role       = "roles/compute.networkUser"

  members = [
    "group:gcp-network-data-team@example.com",
  ]
}
Output
Terraform will create 2 IAM bindings.
Apply complete!
๐Ÿ’กGroups Over Individuals
Bind IAM roles to Google Groups, not individual users. When a team member joins or leaves, update the group, not the IAM policy. This prevents policy bloat and accidental access.
๐Ÿ“Š Production Insight
We found a service project with project-level networkUser that was using a production subnet in us-east1 by accidentโ€”their staging apps ran on production infrastructure. Switching to subnet-level IAM and groups eliminated this class of misconfiguration overnight.
๐ŸŽฏ Key Takeaway
Subnet-level IAM grants least-privilege access to specific subnets; manage with Terraform and Google Groups.
Shared VPC vs. Peered VPC Trade-offs between centralized and distributed network management Shared VPC VPC Peering Network Administration Centralized in host project Distributed across projects Subnet Management Single pool, carved into subnets Separate subnets per project IAM Control Network User role for granular access Project-level IAM per VPC Firewall Rules Centralized, applied to all subnets Per-VPC rules, no global view Scalability Easier to add new service projects Requires new peering for each project Hybrid Connectivity Single VPN/Direct Connect for all Separate connections per VPC THECODEFORGE.IO
thecodeforge.io
Gcp Shared Vpc

Service Account-Based Firewall Rules vs Network Tags

GCP firewall rules traditionally use network tags (targetTags) to determine which VMs a rule applies to. However, network tags can be set by anyone with compute.instanceAdmin role, creating a security gap: a developer can tag a VM as 'http-server' and circumvent intended restrictions. Service account-based firewall rules are more secure because they use IAM identity, which is harder to spoof. Instead of target_tags = ["web-server"], use target_service_accounts = ["web-sa@project.iam.gserviceaccount.com"]. Any VM running as that service account automatically gets the firewall rule. This provides better audit trails and prevents tag-based privilege escalation. For production, prefer service account-based rules for sensitive applications (databases, internal APIs) and use network tags only for coarse-grained controls like health check allowlists. Migrate existing tag-based rules gradually: audit which service accounts should have access, create the rules, then remove tag-based rules.

sa-firewall.tfHCL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
resource "google_compute_firewall" "allow-db-from-app" {
  name    = "fw-allow-db-from-app-sa"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["5432"]
  }

  source_service_accounts = ["app-sa@app-project.iam.gserviceaccount.com"]
  target_service_accounts = ["db-sa@db-project.iam.gserviceaccount.com"]
}

# Compare with tag-based (less secure)
resource "google_compute_firewall" "allow-db-from-app-tag" {
  name    = "fw-allow-db-from-app-tag"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["5432"]
  }

  source_tags = ["app-tier"]
  target_tags = ["db-tier"]
}
Output
google_compute_firewall.fw-allow-db-from-app-sa: Creation complete.
๐Ÿ”ฅService Account Rules Are Immutable Per VM
A VM's service account cannot be changed after creation. Plan your service account strategy before deploying workloads. Use one service account per tier (web, app, db) for consistent firewall enforcement.
๐Ÿ“Š Production Insight
A developer tagged their VM as 'db-tier' to debug a connectivity issue, accidentally exposing it to the app tier's firewall allow rule. With service account-based rules, this wouldn't have been possible because the VM's service account was immutable. We now enforce service account rules for all production firewall policies.
๐ŸŽฏ Key Takeaway
Prefer service account-based firewall rules over network tags for IAM-backed, auditable network security.
⚙ Quick Reference
14 commands from this guide
FileCommand / CodePurpose
enable-shared-vpc.shHOST_PROJECT_ID="network-host-prod"The Shared VPC Architecture
subnets.tfresource "google_compute_subnetwork" "subnets" {Subnet Design
iam-network-user.shHOST_PROJECT="network-host-prod"IAM and Access Control
firewall.tfresource "google_compute_firewall" "allow-health-checks" {Firewall Rules
cloud-nat.tfresource "google_compute_router" "router" {Private Google Access and Cloud NAT
vpc-peering.tfresource "google_compute_network_peering" "peer-with-onprem" {VPC Peering and Hybrid Connectivity
create-gke-cluster.shHOST_PROJECT="network-host-prod"GKE in Shared VPC
flow-logs.tfresource "google_compute_subnetwork" "subnet_with_logs" {Monitoring and Auditing
org-policy.tfresource "google_organization_policy" "restrict_host_projects" {Organizational Policy and Hierarchy
migrate-instance.shOLD_INSTANCE="web-server-old"Migration from VPC Peering to Shared VPC
check-overlap.shPROJECT_A="prod-network-host"Common Pitfalls and How to Avoid Them
main.tfmodule "host_project" {Automation with Terraform and CI/CD
subnet-iam.tfresource "google_compute_subnetwork_iam_binding" "web_team" {Subnet-Level IAM vs Project-Level
sa-firewall.tfresource "google_compute_firewall" "allow-db-from-app" {Service Account-Based Firewall Rules vs Network Tags

Key takeaways

1
Centralized Network Control
Shared VPC decouples network ownership from workload ownership, allowing a central team to manage subnets, firewalls, and egress while service teams deploy freely.
2
IAM Scoping Is Critical
Always use IAM conditions to restrict compute.networkUser to specific subnets, preventing cross-environment access and reducing blast radius.
3
Plan IP Space Generously
Allocate non-overlapping CIDR ranges per environment and region, include secondary ranges for GKE, and use an IPAM tool to avoid conflicts.
4
Automate Everything
Use Terraform and CI/CD to manage Shared VPC resources, enforce policies with OPA, and audit changes to prevent drift and manual errors.

Common mistakes to avoid

3 patterns
×

Ignoring gcp shared vpc best practices

Symptom
Production incidents and unexpected behavior
Fix
Follow GCP documentation and implement proper monitoring and testing
×

Over-provisioning without rightsizing analysis

Symptom
Wasted cloud spend and poor resource utilization
Fix
Use committed use discounts and rightsize based on actual usage metrics
×

Skipping IAM least-privilege principles

Symptom
Security vulnerabilities and compliance violations
Fix
Implement custom roles with minimum required permissions and use conditions
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is GCP Shared VPC: Centralized Network Administration for Multi-Pro...
Q02SENIOR
How do you configure GCP Shared VPC: Centralized Network Administration ...
Q03SENIOR
What are the cost optimization strategies for GCP Shared VPC: Centralize...
Q01 of 03JUNIOR

What is GCP Shared VPC: Centralized Network Administration for Multi-Project and when would you use it in production?

ANSWER
GCP Shared VPC: Centralized Network Administration for Multi-Project is a Google Cloud service for managing infrastructure efficiently. It's ideal for organizations needing scalable, reliable cloud solutions. Use it when you need automated management and consistent performance across your GCP projects.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Can a service project be attached to multiple host projects?
02
How do I grant a service project access to only specific subnets?
03
What happens to existing resources when I attach a service project to a Shared VPC?
04
Can I use Shared VPC with Cloud Run or Cloud Functions?
05
How do I troubleshoot connectivity issues between service projects in the same Shared VPC?
06
Is there a limit to the number of service projects per host project?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
🔥

That's Google Cloud. Mark it forged?

8 min read · try the examples if you haven't

Previous
Cloud NAT & Private Google Access
18 / 55 · Google Cloud
Next
Cloud Load Balancing