Hello

Bonjour

स्वागत हे

Ciao

Olá

おい

Hallå

Guten tag

Hallo

Home

About

Projects

Blog

Published on
· 3 min read

Next.js on Cloud Run + Cloud SQL: 5 errors you will hit

The problem

You have a Next.js app with a Postgres database. It works locally. You deploy it to Cloud Run, point it at Cloud SQL, and every single database query dies with a timeout error.

Turns out there are about five things that can go wrong between Cloud Run and Cloud SQL, and you'll probably hit all of them. Here's each error and how to fix it.

The stack

  • Next.js 16 (App Router, output: "standalone")
  • Drizzle ORM with node-postgres (pg driver)
  • Cloud SQL (PostgreSQL)
  • Cloud Run — containerized with Docker

Error 1: Connection terminated due to timeout

A simple SELECT on the users table. Works locally, blows up on Cloud Run.

What's going on

Cloud Run is serverless — instances spin up and down constantly. The connection to Cloud SQL goes through the Cloud SQL Auth Proxy, not a direct link. TCP connections go stale without anyone noticing.

Here's the pool config most tutorials give you:

Three problems:

  1. No TCP keepalive. Cloud Run's network kills idle connections silently. Your pool holds dead connections and tries to use them. Timeout.
  2. max: 20 is way too high. Each Cloud Run instance creates its own pool. Ten instances = 200 connections. Default Cloud SQL limit is ~100.
  3. 5 second timeout. Cold starts through a VPC connector can take longer than that.

The fix

SettingValueWhy
max5Prevents connection exhaustion across instances
keepAlivetrueSends TCP probes to detect dead connections
keepAliveInitialDelayMillis10000Start probing after 10s idle
connectionTimeoutMillis10000More time for cold starts
allowExitOnIdletrueClean shutdown on scale-down

That pool error handler matters too. Without it, a stale connection error becomes an unhandled rejection and kills the whole process.


Error 2: ENOENT on Unix socket

ENOENT — the socket file doesn't exist.

What's going on

Cloud Run talks to Cloud SQL through the Cloud SQL Auth Proxy, which runs as a sidecar and creates a Unix socket at /cloudsql/INSTANCE_CONNECTION_NAME. But here's the thing: you have to explicitly add the Cloud SQL connection to your Cloud Run service. Setting DATABASE_URL with a socket path doesn't do it.

So if your connection string looks like:

postgresql://user:pass@/dbname?host=/cloudsql/project:region:instance

...but you never added the Cloud SQL connection in Cloud Run, the proxy isn't running and there's no socket.

The fix

Option A: Add the Cloud SQL connection (recommended)

In Cloud Run console: service → Edit & Deploy New Revision → Connections tab → Add connection → select your instance.

Or via CLI:

Option B: Use private IP

If you have a VPC connector set up, use TCP instead:

DATABASE_URL="postgresql://user:pass@PRIVATE_IP:5432/dbname"

Don't set INSTANCE_UNIX_SOCKET in this case.

Google recommends Option A. No VPC config needed, and the Auth Proxy handles authentication for you.


Error 3: 403 Not Authorized

What's going on

The Cloud SQL Auth Proxy runs under the Cloud Run service account. If your Cloud Run and Cloud SQL are in different GCP projects, that service account has no permissions in the other project. Even in the same project, the default Compute Engine service account might not have cloudsql.client.

The fix

If it's cross-project, grant the role in the Cloud SQL project. Not the Cloud Run project. Easy to mix up.


Error 4: Password authentication failed

Wrong password in the env var. Simple, but easy to miss when you're copy-pasting between multiple GCP projects and env configs.

The fix


Full checklist

Code:

  • next.config.tsoutput: "standalone"
  • src/db/index.ts — pool config with keepAlive, low max, Unix socket support
  • Dockerfile — standard Next.js standalone build

GCP:

  • Cloud SQL instance with a database and user
  • Cloud Run deployed with --add-cloudsql-instances
  • Service account has roles/cloudsql.client on the Cloud SQL project
  • Env vars set: INSTANCE_UNIX_SOCKET, DB_USER, DB_PASS, DB_NAME

Database:

  • Cloud SQL Auth Proxy installed locally
  • Migrations applied via the proxy

Sanity check:

  • Auth flow works (usually the first query to hit the DB)
  • No timeout errors in Cloud Run logs
  • Pool errors are logged, not crashing the process

What to remember

keepAlive: true on the pg Pool. That's the single most important thing. Without it, idle connections die silently and queries hang until they timeout.

Keep max low — 3 to 5. Each Cloud Run instance makes its own pool, and those add up fast with autoscaling.

Use Unix sockets over TCP. Less config, more reliable, and the Auth Proxy handles auth automatically.

If Cloud Run and Cloud SQL are in different projects, the IAM grant goes in the Cloud SQL project. Not the other one.

Run migrations locally through the Cloud SQL Auth Proxy. Don't try to run them from inside Cloud Run.

And add a pool error handler. Without one, a stale connection on an idle client becomes an unhandled rejection that kills your process.


This post only covers the gotchas. If you want a start-to-finish walkthrough — Dockerfile, Cloud Run config, Cloud SQL setup, the whole thing — I'm working on a dedicated tutorial for that. Stay tuned.