All posts

Connecting Cloud Run to Cloud SQL: the /cloudsql socket, connectors, private IP and the errors of the first deploy

Three ways from Cloud Run to Cloud SQL: the /cloudsql socket, a connector library, or a private IP. The commands, the connection string, the cost, and the errors of the first deploy.

Cloud Run connects to Cloud SQL in one of three ways: the built-in /cloudsql Unix socket, turned on with one deploy flag and the Cloud SQL Client role; a Google connector library inside the app; or a private IP reached through Direct VPC egress on port 5432. Start with the socket: it is encrypted, checked by IAM, needs no IP allowlist and has no charge in the same region.

In short

  • --add-cloudsql-instances plus the Cloud SQL Client role gives a service a Unix socket at /cloudsql/PROJECT:REGION:INSTANCE, tunnelled over TLS to port 3307.
  • Each Cloud Run instance opens at most 100 connections through the socket; the smallest Cloud SQL instance accepts 25 by default.
  • A socket path is limited to 108 characters, and pg8000 wants the .s.PGSQL.5432 file name where other drivers take the directory.
  • Same-region traffic costs $0; a public IPv4 address costs $0.01 an hour only while the instance is stopped.

Pages were read and commands checked on October 12, 2026.

Three ways in

A Cloud Run service has no fixed IP address and no server you can log in to, so a host and port in DATABASE_URL, an IP allowlist or a tunnel installed next to the app do not carry over as they are. This guide covers Cloud SQL for PostgreSQL; Google keeps a version of its Cloud Run page for MySQL and for SQL Server. Which database to choose is in Cloud SQL, Firestore or a serverless Postgres, and what an instance costs is in the smallest Cloud SQL instances.

  • The built-in connection. Deploy with --add-cloudsql-instances and the instance's connection name, give the service's account (the identity it runs as) the Cloud SQL Client role, and point your driver at the Unix socket /cloudsql/PROJECT_ID:REGION:INSTANCE, a file-like address that programs on one machine talk through instead of a network port. The instance keeps its default public IP, needs no allowlist, and the traffic is encrypted. Permissions are checked by IAM, Google Cloud's permission system. Start here.
  • A connector library. Google's Java, Python, Go and Node.js connectors do the same work inside your process. Use one for Java, which has no Unix sockets, for logging in to the database as the service account with no password, or when 100 connections per instance is too few.
  • A private IP. The instance gets an address inside your VPC network (your private network in Google Cloud), the service reaches it through Direct VPC egress, and your driver connects to that address on port 5432. Use it when the instance must have no public IP at all.
A diagram of three paths from a Cloud Run service to Cloud SQL: the built-in /cloudsql socket handled by Cloud Run, a connector library inside the app, both reaching the instance's public IP on port 3307 with TLS and an IAM check, and Direct VPC egress reaching a private IP on port 5432 with your own TLS settings
Three ways from a Cloud Run service to Cloud SQL. The first two check IAM and encrypt for you; the third relies on the network and the password

What the flag does

When a revision has a Cloud SQL connection, Cloud Run runs a Cloud SQL Auth Proxy for it, a Google program that opens an encrypted tunnel to the database after an IAM check, so your image does not have to contain one, and mounts a directory at /cloudsql. Inside it is one Unix socket per instance, named after the instance connection name, the project:region:instance string that identifies a Cloud SQL instance. Your driver talks plain Postgres to that socket, as it would to a database on the same machine.

The proxy does the rest. On the first connection it asks the Cloud SQL Admin API, the interface that manages instances, for a short-lived client certificate, which expires in about an hour and is refreshed before that, and opens a TLS tunnel to the instance's public IP on port 3307. The Admin API only issues that certificate to an identity with the cloudsql.instances.connect permission, which is in the Cloud SQL Client role. So the gate is IAM, not an IP allowlist, and the instance's public IP does not need an authorized network.

A sequence of what happens on the first query: the app opens the Unix socket, Cloud Run's proxy asks the Cloud SQL Admin API for a certificate that lasts about an hour, the API checks the Cloud SQL Client role of the service's account, the proxy opens TLS to the instance on port 3307, and Postgres then checks the database user and password
Two checks, two systems. IAM decides whether the service may reach the instance; Postgres decides whether the user and password may log in

Two checks follow from this, and they fail with different errors. IAM decides whether the service may reach the instance at all. Postgres then checks the user name and password like any Postgres. When a connection fails, the error shows which of the two refused it, as the list at the end of this guide does.

Three more facts about the flag matter later:

  • Each Cloud Run instance can open at most 100 connections through it. Google's quotas page says the limit applies only to this built-in connection.
  • It uses your Admin API quota. Google estimates the quota used at about twice the number of Cloud SQL instances times the number of Cloud Run instances running, and the Connect category allows 1,000 requests a minute per user per region by default.
  • The first generation execution environment, the older of Cloud Run's two runtimes, embeds Auth Proxy v1, which connects only to instances with the default per-instance certificate authority, the signer of the instance's certificate. More on that under "What breaks".

Step 1: the instance and its connection name

The Admin API must be on in the project the service runs in. If the instance lives in another project, it must be on in both, and the role is granted in the instance's project.

gcloud services enable sqladmin.googleapis.com
gcloud sql instances describe app-db --format='value(connectionName)'

The second command prints the connection name, such as PROJECT_ID:europe-west1:app-db. Put the instance in the service's region: Google recommends it for latency, for cost and so that one region's failure does not take both down (choosing a Cloud Run region covers the choice). A new instance gets a public IP by default, and nothing else is needed for the built-in connection. The same describe command shows whether the instance has daily backups and point-in-time recovery on; backups and point-in-time recovery on Cloud SQL lists the settings to check on day one.

Step 2: a database user and its password

Create the user the app logs in as, with a password generated into a file only you can read:

umask 077
openssl rand -base64 24 | tr -d '/+=\n' > db-pass.txt
gcloud sql users create app --instance=app-db --password="$(cat db-pass.txt)"
gcloud sql databases create app --instance=app-db

users create has no prompt option, so the password is on your machine's command line for a moment; to change it later without that, gcloud sql users set-password app --instance=app-db --prompt-for-password asks for it instead. A user created this way gets the cloudsqlsuperuser role, which can create databases and roles; for an app that only reads and writes its own tables, grant it less with GRANT from psql.

Then put the password in Secret Manager and let the service's account read it, as secrets on Cloud Run explains step by step. Give the service its own service account, so the roles you grant apply to this service only (IAM for a Cloud Run app has the full set of roles):

gcloud iam service-accounts create app-run
gcloud secrets create db-pass --data-file=db-pass.txt
rm db-pass.txt
gcloud secrets add-iam-policy-binding db-pass \
  --member=serviceAccount:app-run@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/secretmanager.secretAccessor
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:app-run@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/cloudsql.client

Step 3: deploy with the connection

gcloud run deploy app --image=IMAGE --region=europe-west1 \
  --service-account=app-run@PROJECT_ID.iam.gserviceaccount.com \
  --add-cloudsql-instances=PROJECT_ID:europe-west1:app-db \
  --set-env-vars=DB_SOCKET=/cloudsql/PROJECT_ID:europe-west1:app-db,DB_USER=app,DB_NAME=app \
  --set-secrets=DB_PASS=db-pass:1

--add-cloudsql-instances appends to the instances a service already has; --set-cloudsql-instances replaces the list, --remove-cloudsql-instances and --clear-cloudsql-instances take instances away. The connection is part of the revision, so every later revision keeps it until you change it. db-pass:1 pins the secret's version, which Google recommends over latest for environment variables. Jobs, such as a migration job, take --set-cloudsql-instances on gcloud run jobs deploy; database migrations as Cloud Run jobs shows the whole job.

Step 4: the connection string your driver wants

This step depends on the driver (Node, Python, Rails and Go on Cloud Run has the rest of each stack's setup), because each one spells "connect to a Unix socket in this directory" differently. The rule underneath is Postgres's own: a host that starts with a slash is a directory holding a socket file named .s.PGSQL.5432, not a machine.

To check the spellings, we ran Postgres 17 listening only on a Unix socket and mounted its directory into client containers under /cloudsql/, named like a real instance, the path Cloud Run uses. The clients had no network at all, so a pass could only come through the socket. The script is in our research notes, runs with Docker, and touches no project.

A terminal with the local test: psql with the socket directory in the host query parameter connects; psql with the path in place of the host fails and looks in /var/run/postgresql; node-postgres 8.23.1 connects with host set to the directory and with the URL form; psycopg 3.3.6 connects; pg8000 fails with the directory and connects with the .s.PGSQL.5432 file name added
Real output of our local copy of the /cloudsql socket, October 12, 2026. ICN is the instance connection name; each driver finds the socket only where it expects it

The spellings that worked, and those Google's and the vendors' pages give for the rest, with S standing for the socket directory, /cloudsql/ followed by the connection name:

URL (libpq, psql, Rails)  postgresql://app:PASSWORD@/app?host=S
node-postgres             { host: 'S', user, password, database }
Prisma (ORM 6)            postgresql://app:PASSWORD@localhost/app?host=S
psycopg, Django           host='S'          Django: HOST = 'S'
SQLAlchemy + psycopg2     postgresql+psycopg2://app:PASSWORD@/app?host=S
pg8000                    unix_sock='S/.s.PGSQL.5432'
Go, pgx                   host=S user=app password=PASSWORD dbname=app
PHP PDO                   pgsql:dbname=app;host=S
.NET Npgsql               Host=S;Username=app;Password=PASSWORD;Database=app
Java                      jdbc:postgresql:///app?cloudSqlInstance=PROJECT_ID:europe-west1:app-db
                            &socketFactory=com.google.cloud.sql.postgres.SocketFactory

What the test and the pages add to that list:

  • URL-based drivers want the directory in the `host` query parameter. Putting the path where the host name usually goes fails: in our run libpq ignored it and looked in its default /var/run/postgresql. Percent-encode the password if it has characters such as @ or /. Prisma's page also wants a dummy host name, which it ignores; we did not run Prisma.
  • node-postgres passed with host in the config object and with the URL as connectionString; psycopg passed with host.
  • pg8000 wants the socket file, not the directory. With the directory it failed with "communication error". Google's page warns that some libraries need the .s.PGSQL.5432 suffix.
  • Java: Google's sample notes that "Unix sockets are not natively supported in Java" and uses the Cloud SQL Java connector as a JDBC socket factory, written on one line in the real URL.

A socket path on Linux is limited to 108 characters. /cloudsql/, the connection name and /.s.PGSQL.5432 together must fit, so very long project or instance names cannot use the socket at all.

Then check what the service really has. These commands only read. The first shows the connection name, whether the public IP is on and the instance's TLS settings; the second shows whether the revision has the connection and whether an execution environment is set. On our own project, where one of eight services uses Cloud SQL, they printed this:

gcloud sql instances describe app-db \
  --format='yaml(connectionName,settings.ipConfiguration)'
gcloud run services describe app --region=europe-west1 --format=yaml \
  | grep -E 'cloudsql|execution-environment'
A terminal on our own project: the instance's connection name, masked; public IP on, the per-instance CA and ENCRYPTED_ONLY; eight services; the service's settings show a cloudsql-instances annotation and no execution environment
Read-only gcloud on our own project, October 12, 2026. Names are masked

Exodus team: our instance is set to accept encrypted connections only, and the service attached through the socket still connects, because Google's SSL page counts connections through the Auth Proxy and the connectors as encrypted. The setting matters for direct connections, not for the socket.

How many connections

The socket takes care of the network, not of the count. Every Cloud Run instance opens its own pool, so the database sees instances times pool size, and Postgres on the smallest Cloud SQL machine accepts 25 connections by default. When the limit is reached, a new connection fails with a Postgres error; on a local Postgres 17 with the limit set low, the text was "remaining connection slots are reserved for roles with the SUPERUSER attribute".

Connection pooling on Cloud Run shows how to size the pool and --max-instances together, what to set in each library, and what to do when the numbers do not fit, including Cloud SQL's own pooler. Concurrency on Cloud Run has the instance side of the sum, so neither is repeated here.

When to use a connector library

The connectors are Google's libraries for Java, Python, Go and Node.js. They do in your process what Cloud Run's proxy does outside it: fetch the certificate, check IAM, open the TLS tunnel. They need the same Cloud SQL Client role and the Admin API, and they do not need --add-cloudsql-instances. Choose one when:

  • The app is Java. Without Unix sockets, the Java connector is how Google's samples connect at all.
  • You want no database password. With automatic IAM database authentication, the service account logs in as a Postgres user and the connector supplies a token that lasts one hour, refreshing it for pooled connections. Google documents it for the Auth Proxy and the Go, Java and Python connectors; the Node.js connector's README shows it with authType: 'IAM'.
  • 100 connections per instance is too few. The limit does not apply to the connectors.
  • You want to choose private or public IP in code, with ip_type or ipTypes.

Google's samples set the connectors' refresh to lazy, "recommended for serverless environments to avoid background refreshes from throttling CPU": with request-based billing an instance gets CPU only while it serves requests, so a refresh scheduled in the background can stall.

For IAM login, the instance needs the flag cloudsql.iam_authentication=on, which instances created in the console have by default. Beware that gcloud sql instances patch --database-flags replaces the whole list of flags, so include the ones you already set. Then create the database user for the service account, named after its email without .gserviceaccount.com, and grant the role:

gcloud sql users create app-run@PROJECT_ID.iam \
  --instance=app-db --type=cloud_iam_service_account
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member=serviceAccount:app-run@PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/cloudsql.instanceUser

The new user can log in but owns nothing, so grant it access to the app's tables with GRANT. On your laptop, the Auth Proxy does the same job: cloud-sql-proxy --port 5432 PROJECT_ID:europe-west1:app-db listens on 127.0.0.1:5432 with your own gcloud auth application-default login, and --auto-iam-authn turns on IAM login.

Private IP

A private IP removes the instance from the internet entirely, which some companies require. It takes more setup and moves two jobs to you.

The instance joins your VPC network through private services access, a link between your network and the one Google's managed services live in, for which you reserve an address range of at least /24 (Google recommends /16). The service then needs a path into that network, and Google recommends Direct VPC egress, which gives its instances addresses in one of your network's subnets (address ranges) and adds no charge beyond network traffic:

gcloud run deploy app --image=IMAGE --region=europe-west1 \
  --network=default --subnet=default --vpc-egress=private-ranges-only \
  --set-env-vars=DB_HOST=PRIVATE_IP --set-secrets=DB_PASS=db-pass:1

private-ranges-only is the default and sends only private addresses through the network. The driver connects to the instance's private IP on port 5432 over TCP, with no proxy in between. That is the first job that moves to you: Google says to enforce SSL on the instance and set it up in the driver, sslmode=require for libpq. The second is that the IAM check is gone, so the network and the password are all that stand between the service and the database. The subnet size, firewall rules and cold-start notes for Direct VPC egress are in moving a Compute Engine VM to Cloud Run.

What it costs

The connection itself has no price on Google's pricing pages; you pay for the instance, its disk and the traffic. Traffic between Cloud Run and Cloud SQL in the same region is free on both sides: Cloud Run's pricing page charges nothing for transfer to Google Cloud resources in the same region, and Cloud SQL's charges nothing for traffic into the instance or out to Google products on the same continent. Our own bill showed the instance's network lines at $0, as the smallest instances guide shows.

Two charges appear around it. A public IPv4 address costs $0.01 an hour only while the instance is idle, that is, stopped; a running instance shows no IP line. And on the private path, Direct VPC egress has no charge of its own, while the older Serverless VPC Access connector runs at least two VMs that bill even with no traffic. A service in another region than its database pays inter-region transfer on Cloud Run's side and adds a longer round trip to every query; egress on Google Cloud has the prices.

What breaks

  • "No such file or directory" on the socket path. The revision has no Cloud SQL connection, the connection name has a typo, or the driver got the path in the wrong place and looked elsewhere, as psql did in our test. Check the annotation with the services list command above.
  • "Cloud SQL connection failed ... ensure that the account has access ... NOT_AUTHORIZED" in the service's logs. The IAM check failed: the service's account lacks the Cloud SQL Client role, it runs as a different account than you granted, or the Admin API is off in one of the two projects.
  • A login error from Postgres. The IAM check passed and the password or user is wrong, for example a password changed on the instance but not in the secret version the revision reads.
  • "remaining connection slots are reserved". The pool times the instances passed max_connections.
  • Connections fail after the instance's CA mode changed. The shared and customer-managed CA modes do not accept connections from the first generation execution environment, and a service with no environment set may run in either. Deploy with --execution-environment=gen2 before you change the mode, and remember that per-instance CA, the default, cannot be chosen again afterwards.
  • 429 rateLimitExceeded from the Admin API. Too many instances fetching certificates in the same minute. Cap --max-instances, as Google suggests.
  • A socket path over 108 characters. Shorten the instance name, or use a connector.

Checklist

  1. Enable the Cloud SQL Admin API in the service's project, and in the instance's if it differs.
  2. Put the instance in the service's region and copy its connection name.
  3. Create a dedicated service account with Cloud SQL Client and access to the password secret only.
  4. Deploy with --add-cloudsql-instances and the password from Secret Manager, pinned to a version.
  5. Write the connection string in the form your driver wants, and test it locally against a socket directory before the first deploy.
  6. Size the pool and --max-instances against max_connections.
  7. Java, passwordless login or more than 100 connections an instance: use a connector library with lazy refresh.
  8. No public IP allowed: private services access, Direct VPC egress, SSL enforced on the instance and in the driver.
  9. Set --execution-environment=gen2 before moving the instance to a shared or customer-managed CA.

Where Exodus comes in

When Exodus audits a repository, it reads how the app connects today: the DATABASE_URL and its driver, the ORM, the pool settings. The plan lists the connection string the app needs on Cloud Run for its own driver, the role and the flag, and checks the pool against the instance's limit, so the agent that runs the move writes the right line the first time.

FAQ

Do I need a VPC connector to reach Cloud SQL from Cloud Run?

No. The built-in connection reaches the instance's public IP over TLS after an IAM check, with no VPC setup and no allowlist. Only a private IP needs a path into your network, and there Google recommends Direct VPC egress, which has no charge of its own, over a Serverless VPC Access connector that bills at least two VMs.

Why does Cloud Run log NOT_AUTHORIZED when connecting to Cloud SQL?

The IAM check failed before Postgres was reached. The service's account lacks the Cloud SQL Client role, the service runs as a different account than the one you granted, or the Cloud SQL Admin API is off in the service's project or the instance's. A wrong password shows up as a Postgres login error instead.

How many connections can a Cloud Run instance open to Cloud SQL?

At most 100 per Cloud Run instance through the built-in /cloudsql connection; the limit does not apply to the connector libraries or a private IP. The database's own limit is usually lower: Postgres on the smallest Cloud SQL instance accepts 25 by default, so the pool size times the instances has to fit under it.

How does a Java app on Cloud Run connect to Cloud SQL?

Through Google's Cloud SQL Java connector, used as a JDBC socket factory, because Java has no native Unix sockets. The URL names the database, the instance connection name in cloudSqlInstance and the connector's SocketFactory class. It needs the same Cloud SQL Client role and the Admin API, but not --add-cloudsql-instances.

Sources

28
  1. docs.cloud.google.comConnect from Cloud Run, Cloud SQL for PostgreSQL, Google
  2. docs.cloud.google.comChoose how to connect to Cloud SQL, Google
  3. docs.cloud.google.comAbout the Cloud SQL Auth Proxy, Google
  4. docs.cloud.google.comConnect using the Cloud SQL Auth Proxy, Google
  5. docs.cloud.google.comCloud SQL Language Connectors overview, Google
  6. docs.cloud.google.comCloud SQL quotas and limits, Google
  7. docs.cloud.google.comAuthorize with SSL/TLS certificates, Cloud SQL for PostgreSQL, Google
  8. docs.cloud.google.comSelect an execution environment for services, Cloud Run, Google
  9. docs.cloud.google.comIAM authentication, Cloud SQL for PostgreSQL, Google
  10. docs.cloud.google.comLog in using IAM database authentication, Google
  11. docs.cloud.google.comManage users with IAM database authentication, Google
  12. docs.cloud.google.comCreate and manage users, Cloud SQL for PostgreSQL, Google
  13. github.comCloud SQL Node.js Connector, GoogleCloudPlatform on GitHub
  14. docs.cloud.google.comLearn about using private IP, Cloud SQL, Google
  15. docs.cloud.google.comCompare Direct VPC egress and VPC connectors, Cloud Run, Google
  16. docs.cloud.google.comManaged Connection Pooling overview, Cloud SQL, Google
  17. docs.cloud.google.comManage database connections, Cloud SQL, Google
  18. cloud.google.comCloud SQL pricing, Google
  19. postgresql.orglibpq connection strings, PostgreSQL 18 documentation
  20. node-postgres.comnode-postgres, Connecting
  21. prisma.ioPrisma ORM 6, PostgreSQL
  22. docs.sqlalchemy.orgSQLAlchemy 2.0, PostgreSQL dialect
  23. docs.djangoproject.comDjango settings, HOST
  24. github.comUnable to connect Cloud SQL with Firebase Functions V2, firebase/firebase-functions issue 1375 on GitHub
  25. docs.cloud.google.comDatabase flags, Cloud SQL for PostgreSQL, Google
  26. docs.cloud.google.comConfigure SSL/TLS certificates, Cloud SQL for PostgreSQL, Google
  27. docs.cloud.google.comgcloud reference, Google Cloud SDK 587.0.0
  28. Exodus team: a local copy of the /cloudsql socket

Exodus moves your app to Cloud Run.

A free web cabinet and an open-source CLI. With your coding agent, they audit your app, show what it costs today and on Google Cloud Run, and move it there step by step.

Sign up for freeSee a sample report
  1. Install the CLIOne line in your terminal, once per machine.
  2. Give the prompt to your agentSign in with Google and copy one prompt. Your agent audits the app on your machine.
  3. Read the report, then moveThe bill today and on Cloud Run, what to change and in what order. The agent does the moving.

Read next

Consult an expert

For a move that is hard to make alone: many services, a large database, no room for downtime. An engineer who has moved production apps to Cloud Run reads what you send and replies by email.

Optional.

What the app does and what it runs on today.

Optional. Anything that would change the plan: compliance, regions, a contract that ends.

Your email goes to our engineers and nowhere else.