If you have ever wondered why your VMs can’t communicate to each other even though everything is connected, you’re not alone. It took me a few tries to understand how VPC peering actually works in Google Cloud Platform.

This guide walks through a real-world scenario: three VPCs that need selective connectivity. We’ll use a PostgreSQL database to prove what works and what doesn’t when dealing with non-transitive routing — something that matters when you’re connecting multiple VPCs together.

What We’re Building

We’re setting up three Virtual Private Cloud networks:

We’ll peer Platform ↔ Data and Data ↔ Security, but Platform will NOT be able to reach Security even though they’re both connected to Data. That’s non-transitive routing.

Architecture Overview

Here’s what the network topology looks like:

Key components:

Why This Matters

In production environments, you can’t just throw everything into one giant VPC. Different teams own different resources. Compliance requirements demand network isolation. But services still need to communicate.

VPC peering gives you that selective connectivity. It’s faster than VPN tunnels (no encryption overhead), cheaper than Shared VPC for multi-tenant scenarios, and more secure than assigning public IPs to all resources.

What is VPC Peering?

VPC Network Peering connects two Virtual Private Cloud networks so that resources in each network can communicate using private IP addresses. Traffic stays within Google’s network and doesn’t traverse the public internet.

How it works:

The critical concept: Non-transitive routing

This is what we’ll prove in this demo. From Google Cloud documentation:

VPC Network Peering is not transitive. If VPC network N1 is peered with N2, and N2 is peered with N3, there is no connectivity between N1 and N3 unless you explicitly create a peering connection between them.

In practical terms:

This design is intentional for security and network isolation. You’ll see this behavior firsthand in Step 8.

Prerequisites

You’ll need:

Step 1: Clone and Prepare

Clone the repository:

git clone https://github.com/misskecupbung/gcp-vpc-peering.git
cd ~/gcp-vpc-peering/

Step 2: Enable APIs

Set your project ID and enable required APIs:

export PROJECT_ID="your-project-id"
gcloud config set project $PROJECT_ID

gcloud services enable compute.googleapis.com
gcloud services enable iap.googleapis.com

These APIs are required for:

Step 3: Configure Terraform Variables

Navigate to the Terraform directory and set up your configuration:

cd ~/gcp-vpc-peering/terraform

cp terraform.tfvars.example terraform.tfvars

# Update the project ID in `terraform.tfvars`
sed -i "s/your-project-id/$PROJECT_ID/" terraform.tfvars

# Verify the configuration:
cat terraform.tfvars

You should see your actual project ID in the file.

Step 4: Deploy the Infrastructure

Initialize Terraform:

terraform init

Run the command below to see what resources will be created:

terraform plan

And then apply:

terraform apply

Type yes when prompted. This creates:

The apply takes about 2–3 minutes. After that, wait another 2–3 minutes for the startup scripts to install packages and configure PostgreSQL.

Note: VPC peering might show INACTIVE immediately after creation. Wait 30–60 seconds and run terraform refresh — peering connections need a moment to establish.

Check the outputs:

terraform output

You’ll see the internal IPs and peering status (should be ACTIVE).

Verify Peering Status

Before testing connectivity, confirm that all VPC peering connections are active:

gcloud compute networks peerings list --network=vpc-platform
gcloud compute networks peerings list --network=vpc-data
gcloud compute networks peerings list --network=vpc-security

You should see output like:

All peering connections should show STATE: ACTIVE

You can also check peering status in the Google Cloud Console:

  1. Go to VPC networkVPC network peering in the Cloud Console and you’ll see all peering connections listed

Each peering appears twice (once from each VPC’s perspective):

Step 5: Verify Basic Connectivity

Let’s check if app-vm in Platform VPC can reach data-vm in Data VPC (they’re peered):

cd ~/gcp-vpc-peering/terraform

DATA_VM_IP=$(terraform output -raw data_vm_ip)

# Ping
gcloud compute ssh app-vm --zone=us-central1-a --tunnel-through-iap \
--command="ping -c 3 $DATA_VM_IP"

You should see successful ping responses:

Now try with curl:

cd ~/gcp-vpc-peering/terraform

DATA_VM_IP=$(terraform output -raw data_vm_ip)

# HTTP (nginx)
gcloud compute ssh app-vm --zone=us-central1-a --tunnel-through-iap \
--command="curl -s http://$DATA_VM_IP"

You’ll see Hello from data-vm. The VPC peering works.

View Peering Routes in Console

To see how VPC peering creates routes automatically:

  1. Go to VPC network → Routes
  2. Filter by Network→ Select vpc-data (for example)
  3. You’ll see routes like:

These routes are automatically created by GCP when you establish peering. You can’t edit or delete them manually — they exist as long as the peering exists.

Step 6: Test the PostgreSQL Database

Here’s where it gets interesting. Our data-vm is running PostgreSQL with a sample database. Let’s first check if PostgreSQL is accepting connections:

cd ~/gcp-vpc-peering/terraform

DATA_VM_IP=$(terraform output -raw data_vm_ip)

# Check if PostgreSQL port is reachable
gcloud compute ssh app-vm --zone=us-central1-a --tunnel-through-iap \
--command="pg_isready -h $DATA_VM_IP -U appuser"

Expected output: 10.2.0.2:5432 — accepting connections

This confirms:

Now query the sample data:

gcloud compute ssh app-vm --zone=us-central1-a --tunnel-through-iap \
--command="PGPASSWORD=changeme123 psql -h $DATA_VM_IP -U appuser -d appdb -c 'SELECT * FROM orders;'"

You should see order data:

This proves that:

  1. Network connectivity works across VPC peering
  2. PostgreSQL is configured correctly for remote access
  3. The firewall rule allows TCP/5432 between peered VPCs

View Firewall Rules in Console

To see which firewall rules allow this cross-VPC database access:

  1. Go to VPC network → Firewall
  2. Filter by Network → Select vpc-data
  3. You’ll see these rules:

4. Click on data-allow-from-platform to see details:

This rule allows app-vm (10.1.0.10) in vpc-platform to reach PostgreSQL on data-vm (10.2.0.2:5432).

Similarly, each VPC has specific rules:

Notice how the rules are explicit about which source network can access which ports. This is important for security.

Step 7: Test data-vm → security-vm Connectivity

Let’s verify that data-vm can reach security-vm (they ARE peered):

cd ~/gcp-vpc-peering/terraform

SECURITY_VM_IP=$(terraform output -raw security_vm_ip)

gcloud compute ssh data-vm --zone=us-central1-a --tunnel-through-iap \
--command="ping -c 3 $SECURITY_VM_IP"

Now try HTTP:

cd ~/gcp-vpc-peering/terraform

SECURITY_VM_IP=$(terraform output -raw security_vm_ip)

gcloud compute ssh data-vm --zone=us-central1-a --tunnel-through-iap \
--command="curl -s http://$SECURITY_VM_IP"

Expected: Hellow from security-vm

This works because data ↔ security are directly peered.

Step 8: Prove Non-Transitive Routing

Now the key test. Platform is peered with data, data is peered with security. Can platform reach security?

cd ~/gcp-vpc-peering/terraform

SECURITY_VM_IP=$(terraform output -raw security_vm_ip)

# This will FAIL — timeout expected
gcloud compute ssh app-vm --zone=us-central1-a --tunnel-through-iap \
--command="ping -c 3 -W 3 $SECURITY_VM_IP"

Expected: 100% packet loss. app-vm cannot reach security-vm even though both are peered with vpc-data.

This is non-transitive routing— the most important thing to understand about VPC Peering. Each peering is an independent connection. Traffic does not hop through an intermediate VPC.

If you need platform to reach security, you’d have to create a third peering directly between them.

Verify Non-Transitive Behavior in Console

You can see this in the Cloud Console by checking routes:

1. Go to VPC network → Routes

2. Use the Network filter → Select vpc-platform

3. Filter by Route typePeering subnet route

You’ll see:

This proves that routes are NOT transitive. vpc-platform only gets routes for directly peered networks.

Step 9: Verify Private Routing

Let’s use traceroute to see the network path:

From app-vm to data-vm (should work):

cd ~/gcp-vpc-peering/terraform

DATA_VM_IP=$(terraform output -raw data_vm_ip)

gcloud compute ssh app-vm --zone=us-central1-a --tunnel-through-iap \
--command="sudo traceroute -I $DATA_VM_IP"

You should see a single hop — traffic goes directly between VPCs via the peering connection, not through any gateway or public internet.

Cleanup

When you’re done testing, destroy all resources:

cd ~/gcp-vpc-peering/terraform
terraform destroy

Type yes to confirm. This removes all VPCs, VMs, peering connections, and firewall rules.

Resources

<hr><p>Understanding VPC Peering and Non-Transitive Routing in GCP was originally published in Google Cloud - Community on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>