Pattern — a Cloud Run service reaching Cloud SQL with no database credential
The end-to-end handoff for the commonest shape in a Google Cloud application: a Cloud Run service, its own service account, a private-IP Cloud SQL instance, IAM database authentication through a connector, and the three grants that carry it. With the known-bad configurations and the 503 they produce.
markdownpatternv1ANX6-HFVEstage: deploytrust: officialfamily: computeservice: cloud-runinterface: anycloud run to cloud sqlIAM database authenticationroles/cloudsql.clientprivate IPcloud SQL connectorroles/secretmanager.secretAccessor503known-bad configurationconnection poolfailover
Cloud Run → Cloud SQL, with no password
The handoff
Cloud Run service (its OWN service account, attached)
│ roles/cloudsql.client ← may open the connection
│ roles/secretmanager.secretAccessor ← only if it must read a secret at all
▼
Cloud SQL connector (Language Connector, or the Auth Proxy)
│ presents a SHORT-LIVED ACCESS TOKEN for the service account
▼
Cloud SQL instance (PRIVATE IP, reachable from the VPC)
│ the database user IS the IAM principal
▼
in-database GRANTs decide what it may read and write
Build order: guides/databases/connect-to-cloud-sql-with-iam-auth.md. The deploy side:
guides/compute/deploy-a-container-on-cloud-run.md.
The three gates, which are three different systems
| Gate | Decides | Grant |
|---|---|---|
| IAM on the instance's project | may this identity open a connection | roles/cloudsql.client |
| the database's own user model | who it is in the database | an IAM database authentication user |
the database's GRANTs | what it may read and write | SQL, not IAM |
All three are required and none implies another. An identity with the first two connects successfully and
can do nothing — which looks like a broken configuration and is a missing GRANT.
Known-bad configurations
1. A password in Secret Manager
It works. It is still the wrong pattern: a credential exists, so it can leak, must rotate, and appears in an
inventory. IAM database authentication removes the credential rather than protecting it. If the design
needs a secret at all, make sure the runtime identity has roles/secretmanager.secretAccessor — the viewer
role cannot read a payload, and that mistake is the commonest cause of the 503 below.
2. A public IP with an authorized-networks allowlist
The vendor's own instruction: *"If you're connecting to an instance by a public IP address, use a Cloud SQL connector (either the Cloud SQL Auth Proxy or one of the Cloud SQL Language Connectors)."* An address allowlist is the weaker answer and ages badly — Cloud Run egress addresses are not a fixed set unless you make them one.
3. The default service account
The default Compute Engine service account is shared, broadly privileged, and sometimes deleted by a tidy
administrator — which produces the deploy-time "User does not have permission to access namespace". One
service account per service, passed with --service-account.
4. A connection pool that does not reconnect
Cloud SQL HA fails over to a standby VM in another zone, and a maintenance update requires an instance restart. Both are designed, scheduled events. A pool that treats a dropped connection as fatal turns either one into an outage. Configure the maintenance window too — the default is an arbitrary hour.
5. A pool sized per instance in a service that scales to many instances
Cloud Run scales out. maxConnections is per process, so the database sees pool size × instance count.
Size the pool against the instance ceiling, not against one container.
The failure you will actually meet
HTTP 503 The request failed because the instance could not start successfully.
Documented causes: missing secrets, IAM, network. Check in that order:
- can the runtime identity read the secret? (
secretAccessor, notviewer) - does it hold
roles/cloudsql.client? - is the egress path to the private IP open?
A 429 is something else entirely — capacity, not configuration
(reference/compute/errors.md §4).
Verification
| Test | Expected |
|---|---|
| the service answers a request that touches the database | 200 |
| grep the whole deployment for a database password | nothing |
remove roles/cloudsql.client and redeploy | the connection fails — proving which control carries it |
| trigger or await a failover | the service reconnects |
| scale to the instance ceiling under load | the database connection count stays within its limit |