Use Case: You have AlloyDB (Project A) and Cloud Run (Project B) with VPC peering, but need to add the nginx proxy to enable connectivity.
Time Required: ~15 minutes
✅ You already have:
- AlloyDB cluster running in Project A (with Private Service Access)
- Cloud Run service in Project B
- VPC peering between Project A and Project B VPCs
- AlloyDB IP address (internal/private IP)
❓ Need to find your AlloyDB IP:
gcloud alloydb instances describe YOUR-INSTANCE-NAME \
--cluster=YOUR-CLUSTER-NAME \
--region=YOUR-REGION \
--project=YOUR-PROJECT-A-ID \
--format="value(ipAddress)"# Set variables
export PROJECT_A_ID="your-project-a-id"
export REGION="us-central1"
export ZONE="${REGION}-a"
export ALLOYDB_IP="10.170.0.2" # Replace with your AlloyDB IP
export PROXY_VM_NAME="alloydb-proxy"
export VPC_NETWORK="project-a-vpc" # Your Project A VPC name
export SUBNET_NAME="project-a-subnet" # Your Project A subnet name
# Create the VM
gcloud compute instances create $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONE \
--machine-type=e2-micro \
--network-interface=subnet=$SUBNET_NAME,no-address \
--tags=alloydb-proxy \
--metadata=startup-script='#!/bin/bash
# Install nginx with stream module
apt-get update
apt-get install -y nginx libnginx-mod-stream
# Create nginx configuration
cat > /etc/nginx/nginx.conf <<'"'"'EOF'"'"'
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
# Load dynamic modules
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
# TCP proxy for PostgreSQL
stream {
upstream alloydb_backend {
server '"$ALLOYDB_IP"':5432 max_fails=3 fail_timeout=30s;
}
server {
listen 5432;
proxy_pass alloydb_backend;
proxy_connect_timeout 5s;
proxy_timeout 300s;
proxy_buffer_size 16k;
}
log_format postgres '"'"'$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time "$upstream_addr"'"'"';
access_log /var/log/nginx/postgres_access.log postgres;
error_log /var/log/nginx/postgres_error.log;
}
EOF
# Start nginx
systemctl enable nginx
systemctl restart nginx
'Note: The startup-script will run automatically when the VM boots.
export PROXY_IP=$(gcloud compute instances describe $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONE \
--format="value(networkInterfaces[0].networkIP)")
echo "Proxy VM IP: $PROXY_IP"Save this IP - you'll need to configure your Cloud Run service to connect to it.
# Set Project B variables
export PROJECT_B_VPC_CIDR="10.2.0.0/20" # Replace with your Project B VPC CIDR
export VPC_CONNECTOR_CIDR="10.8.0.0/28" # Replace with your VPC Connector CIDR
# Create firewall rule
gcloud compute firewall-rules create allow-from-project-b \
--project=$PROJECT_A_ID \
--network=$VPC_NETWORK \
--direction=INGRESS \
--priority=1000 \
--action=ALLOW \
--rules=tcp:5432 \
--source-ranges=$PROJECT_B_VPC_CIDR,$VPC_CONNECTOR_CIDR \
--target-tags=alloydb-proxy \
--description="Allow Project B Cloud Run to access AlloyDB through proxy"Important: Include both your Project B VPC CIDR and VPC Connector CIDR.
# SSH to the proxy VM and check nginx
gcloud compute ssh $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONE \
--tunnel-through-iap \
--command="systemctl status nginx && ss -tlnp | grep 5432"Expected output:
- nginx is
active (running) - Port 5432 shows
LISTENwith nginx process
Update your Cloud Run service with the proxy IP:
export PROJECT_B_ID="your-project-b-id"
export CLOUD_RUN_SERVICE="your-cloud-run-service-name"
export DB_USER="postgres"
export DB_PASSWORD="your-db-password"
export DB_NAME="postgres"
gcloud run services update $CLOUD_RUN_SERVICE \
--project=$PROJECT_B_ID \
--region=$REGION \
--set-env-vars="PROXY_VM_IP=$PROXY_IP,DB_USER=$DB_USER,DB_PASSWORD=$DB_PASSWORD,DB_NAME=$DB_NAME,DB_PORT=5432"# Get Cloud Run URL
export SERVICE_URL=$(gcloud run services describe $CLOUD_RUN_SERVICE \
--project=$PROJECT_B_ID \
--region=$REGION \
--format="value(status.url)")
# Test health endpoint
curl $SERVICE_URL/health
# Test database connection
curl $SERVICE_URL/testExpected response:
{
"status": "success",
"connection_time_ms": 40,
"read_test": { "status": "passed" },
"write_test": { "status": "passed" }
}Check 1: Verify VPC peering is ACTIVE
gcloud compute networks peerings list \
--network=$VPC_NETWORK \
--project=$PROJECT_A_IDCheck 2: Verify firewall rule exists
gcloud compute firewall-rules describe allow-from-project-b \
--project=$PROJECT_A_IDCheck 3: Test from proxy VM to AlloyDB
gcloud compute ssh $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONE \
--tunnel-through-iap \
--command="nc -zv $ALLOYDB_IP 5432"Check nginx status and logs:
gcloud compute ssh $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONE \
--tunnel-through-iap \
--command="systemctl status nginx && tail -20 /var/log/nginx/error.log"Restart nginx:
gcloud compute ssh $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONE \
--tunnel-through-iap \
--command="sudo systemctl restart nginx"Verify credentials:
# Test with psql from proxy VM
gcloud compute ssh $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONE \
--tunnel-through-iap \
--command="apt-get install -y postgresql-client && PGPASSWORD='$DB_PASSWORD' psql -h $ALLOYDB_IP -U $DB_USER -d $DB_NAME -c 'SELECT version()'"gcloud compute networks vpc-access connectors describe YOUR-CONNECTOR-NAME \
--region=$REGION \
--project=$PROJECT_B_ID \
--format="value(ipCidrRange)"# Project A VPC CIDR
gcloud compute networks subnets describe YOUR-SUBNET-NAME \
--region=$REGION \
--project=$PROJECT_A_ID \
--format="value(ipCidrRange)"
# Project B VPC CIDR
gcloud compute networks subnets describe YOUR-SUBNET-NAME \
--region=$REGION \
--project=$PROJECT_B_ID \
--format="value(ipCidrRange)"┌─────────────────┐
│ Cloud Run │ Your application in Project B
│ (Project B) │
└────────┬────────┘
│ Connects to PROXY_VM_IP:5432
│
VPC Peering
│
▼
┌─────────────────┐
│ nginx Proxy VM │ Port 5432 → Forwards to AlloyDB
│ (Project A) │
└────────┬────────┘
│ Connects to ALLOYDB_IP:5432
│
▼
┌─────────────────┐
│ AlloyDB │ Your database in Project A
│ (Project A) │
└─────────────────┘
Key Benefits:
- ✅ No AlloyDB downtime or migration
- ✅ Works with existing Private Service Access
- ✅ Bypasses transitive VPC peering limitation
- ✅ Low latency (~40ms)
- ✅ Cost-effective (~$5/month for e2-micro)
# Delete firewall rule
gcloud compute firewall-rules delete allow-from-project-b \
--project=$PROJECT_A_ID
# Delete VM
gcloud compute instances delete $PROXY_VM_NAME \
--project=$PROJECT_A_ID \
--zone=$ZONEIf you prefer infrastructure as code, see terraform/project-a/proxy-vm.tf for the equivalent Terraform configuration.
The gcloud commands above are functionally identical to the Terraform deployment.