Secure Connections: Cloud Run Direct VPC Egress to Cloud SQL
Scenario
You are deploying a Node.js container to Cloud Run. The container must securely connect to a Cloud SQL PostgreSQL database. The database does not have a public IP address. To achieve this, Cloud Run must natively join the VPC network through Direct VPC Egress (which replaces the older Serverless VPC Access Connectors). Furthermore, the database password must be supplied to the container via Secret Manager.
- Provision a VPC network and a regional subnet.
- Provision a Private Services Access peering connection for Cloud SQL.
- Deploy a private Cloud SQL instance (ipv4_enabled = false) with a generated password securely stored in Secret Manager.
- Deploy a Cloud Run v2 service using vpc_access { network_interfaces } attached to the subnet to establish Direct VPC Egress.
Additional Context
Direct VPC Egress vs Connectors: Historically, connecting serverless (Functions, Cloud Run) to a VPC required provisioning a "Serverless VPC Access Connector" (which spun up hidden e2-micro VMs behind the scenes that passed traffic into the VPC, costing extra money). Direct VPC Egress is GCP's newer native network path where Cloud Run instances get IPs directly from a dedicated `/28` subnetwork in your VPC.
Secret Manager Integration: Cloud Run can natively mount secrets as environment variables or volume mounts directly from Secret Manager, without writing SDK code to fetch them, provided the Cloud Run Service Account has `roles/secretmanager.secretAccessor`.
main.tf
provider "google" {
project = var.project_id
region = var.gcp_region
}
# 1. Networking Setup
resource "google_compute_network" "vpc" {
name = "serverless-vpc"
auto_create_subnetworks = false
}
# Subnet for Cloud Run Direct Egress (Requires purpose = PRIVATE_SERVICE_CONNECT or standard, but just standard works for Direct Egress)
resource "google_compute_subnetwork" "serverless_subnet" {
name = "run-subnet"
network = google_compute_network.vpc.id
region = var.gcp_region
cidr_range = "10.0.1.0/28" # Direct egress requires at least a /28
}
# 2. Private Services Access (Required for Private Cloud SQL)
resource "google_compute_global_address" "private_ip_alloc" {
name = "db-private-ip-alloc"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.vpc.id
}
resource "google_service_networking_connection" "private_vpc_connection" {
network = google_compute_network.vpc.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name]
}
# 3. Database Secrets
resource "random_password" "db_pass" { length = 16; special = true }
resource "google_secret_manager_secret" "db_secret" {
secret_id = "db-password"
replication { auto {} }
}
resource "google_secret_manager_secret_version" "db_secret_val" {
secret = google_secret_manager_secret.db_secret.id
secret_data = random_password.db_pass.result
}
# 4. Cloud SQL Private Instance
resource "google_sql_database_instance" "private_db" {
name = "private-pg-db"
region = var.gcp_region
database_version = "POSTGRES_15"
depends_on = [google_service_networking_connection.private_vpc_connection]
settings {
tier = "db-f1-micro"
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.vpc.id
}
}
deletion_protection = false
}
resource "google_sql_user" "users" {
name = "app_user"
instance = google_sql_database_instance.private_db.name
password = random_password.db_pass.result
}
# 5. Cloud Run with Direct VPC Egress
resource "google_cloud_run_v2_service" "app" {
name = "db-app"
location = var.gcp_region
template {
containers {
image = "us-docker.pkg.dev/cloudrun/container/hello" # Dummy image
env {
name = "DB_HOST"
value = google_sql_database_instance.private_db.private_ip_address
}
# Native Secret Manager Integration!
env {
name = "DB_PASS"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.db_secret.secret_id
version = "latest"
}
}
}
}
# Enable Direct VPC Egress connecting straight to the subnet
vpc_access {
network_interfaces {
network = google_compute_network.vpc.id
subnetwork = google_compute_subnetwork.serverless_subnet.name
}
# Route all traffic, or only internal traffic, to the VPC
egress = "PRIVATE_RANGES_ONLY"
}
}
}Workflow Commands
terraform init
terraform apply -var="project_id=my-gcp-project-123"
# Cloud SQL instance creation takes 10+ minutes. Be patient!
# To verify Cloud Run can reach the DB, check the application logs for successful connections
# (assuming you push a real container image doing a DB check).
terraform destroy -var="project_id=my-gcp-project-123"
