When you deploy a Cloud Run service with a Cloud SQL database, the default setup routes traffic over the public internet. The connection is encrypted and Cloud SQL only accepts authorized connections, but there’s a better approach.

Your data doesn’t need to leave your private network. That’s where Serverless VPC Access comes in.

This article shows you exactly how to connect Cloud Run to Cloud SQL PostgreSQL using a VPC connector — keeping all traffic private, reducing latency, and following security best practices.

The Problem with Public Connectivity

By default, Cloud Run is fully serverless — it doesn’t run inside your VPC. When you connect to Cloud SQL from Cloud Run, traffic flows through:

  1. Cloud Run → Public internet
  2. Public internet → Cloud SQL public IP
  3. Authentication via Cloud SQL Proxy or SSL certificates

This works but has downsides:

What is Serverless VPC Access?

Serverless VPC Access is a feature that allows serverless environments (Cloud Run, Cloud Functions, App Engine) to send requests to resources in a VPC network using internal IP addresses.

The connector works by provisioning a dedicated /28 subnet (16 IPs) in your VPC and spinning up e2-micro VMs that forward traffic between Cloud Run and your network. You control which traffic uses it via the —- vpc-egress flag:

A few things worth knowing: connectors are regional, require their own /28 that can’t be shared, and multiple Cloud Run services can reuse the same connector. IPv6 and Shared VPC (unless the connector is in the host project) are not supported.

What We’re Building

Architecture Overview

Key components:

Security benefits:

Prerequisites

Cost: around $2–3 per day for Cloud SQL (cheapest tier). Destroy resources after testing to avoid charges.

Step 1: Clone and Setup

Clone the repository:

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

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 sqladmin.googleapis.com
gcloud services enable vpcaccess.googleapis.com
gcloud services enable run.googleapis.com
gcloud services enable servicenetworking.googleapis.com
gcloud services enable cloudresourcemanager.googleapis.com
gcloud services enable cloudbuild.googleapis.com

These APIs are required for:

API enablement takes 1–2 minutes.

Step 3: Build the Container Image

Before running Terraform, build and push the Flask app container. Terraform will deploy this image to Cloud Run:

cd ~/gcp-serverless-vpc-access/app/

gcloud builds submit --tag gcr.io/$PROJECT_ID/my-api

The build takes about 1–2 minutes. Once complete, the image is stored in Google Container Registry.

Step 4: Configure Terraform Variables

Navigate to the Terraform directory:

cd ~/gcp-serverless-vpc-access/terraform/
cp terraform.tfvars.example terraform.tfvars

Update the project ID:

sed -i 's/your-project-id/'$PROJECT_ID'/g' terraform.tfvars

Verify the configuration:

cat terraform.tfvars

Step 5: Deploy Infrastructure with Terraform

terraform init

Generate an execution plan:

terraform plan

Apply the configuration:

terraform apply

Type yes when prompted.

This creates:

The apply takes 8–12 minutes because Cloud SQL provisioning is slow.

Step 6: Verify Resources from Console

VPC Network

Navigate to VPC network → VPC networks in the console:

You should see vpc-main with:

Serverless VPC Access Connector

Go to Serverless VPC Access in the console:

The connector should show:

Cloud SQL Instance

Navigate to SQL in the console and click on my-instance:

Key details to verify:

Cloud Run Service

Go to Cloud Run and click on my-api:

Verify:

Test VM

Navigate to Compute Engine → VM instances:

The VM should have:

Step 7: Verify Cloud SQL Private Connectivity

Verify the Cloud SQL instance has only a private IP using gcloud:

DB_IP=$(terraform output -raw cloud_sql_private_ip)
echo "Cloud SQL Private IP: $DB_IP"

gcloud sql instances describe my-postgres \
--format="value(ipAddresses)"

You should see only a private IP (something like 10.x.x.x). No public IP.

This confirms the database is not accessible from the internet. Only resources inside the VPC can reach it.

Step 8: Test Cloud Run → Cloud SQL Connection

Get your Cloud Run service URL:

SERVICE_URL=$(terraform output -raw cloud_run_url)
echo "Cloud Run URL: $SERVICE_URL"

Test the root endpoint:

curl -s $SERVICE_URL | jq .

You should see:

{
"service": "my-api",
"status": "healthy"
}

Now test the database connection:

curl -s $SERVICE_URL/db | jq .

If everything works, you’ll see PostgreSQL version information and confirmation of the database connection.

{
"database": "connected",
"host": "10.126.0.3",
"version": "PostgreSQL 14.x on x86_64-pc-linux-gnu, compiled by gcc..."
}

This confirms that:

  1. Cloud Run is routing through the VPC connector
  2. The connector has access to the VPC
  3. Cloud SQL is reachable via private IP
  4. PostgreSQL authentication works

Step 9: Verify Connectivity from Test VM

Let’s double-check that the Cloud SQL instance is truly private by connecting from our test VM:

DB_IP=$(terraform output -raw cloud_sql_private_ip)

gcloud compute ssh test-vm \
--zone=us-central1-a \
--tunnel-through-iap \
--command="PGPASSWORD=changeme123 psql -h $DB_IP -U appuser -d appdb -c 'SELECT version();'"

This proves the database is accessible from inside the VPC but not from the outside.

Cleanup

Destroy resources when done:

cd ~/gcp-serverless-vpc-access/terraform/
terraform destroy

Type yes to confirm.

If you encounter Failed to delete Service Networking Connection.

This happens because the VPC peering connection (servicenetworking.googleapis.com) created by Private Service Connection is still active. Delete it manually:

Wait 30 seconds, then run terraform destroy again.

Resources

<hr><p>Connecting Cloud Run to Cloud SQL Without Going Through the Public Internet was originally published in Google Cloud - Community on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>