"Internal-only" Cloud Run isn't just a checkbox
Making a Cloud Run service private is not one toggle. It is a decision about ingress, routing, caller path, and IAM working together as one access model.
An internal Cloud Run service is the intersection of an allowed ingress path and an authorized caller.
Ingress doesn’t replace IAM. IAM doesn’t make every route private. VPC attachment on the calling service doesn’t automatically make the destination see the request as internal. All three assumptions are common sources of access incidents.
Design private reachability as one flow from caller to service.
Choose the intended front door
Cloud Run services have a stable run.app endpoint even when another frontend is used. Decide which path callers should use:
- the default endpoint reached from an allowed internal source
- an internal Application Load Balancer
- Private Service Connect with private DNS for a VPC-facing address model
- an external Application Load Balancer protected by controls such as IAP or Cloud Armor
Then choose the ingress setting that permits that path and rejects the others.
all allows direct ingress subject to IAM. internal-and-cloud-load-balancing permits recognized internal sources and traffic through an external Application Load Balancer while blocking direct internet ingress to the service URL. internal restricts ingress to sources Cloud Run recognizes as internal, including supported VPC and Google-managed paths.
The middle mode is often the correct public-edge architecture: users enter through the load balancer, while the default service endpoint isn’t another internet path around it.
“Internal” is a platform definition
Cloud Run doesn’t interpret internal as “same company,” “same organization,” or “the hostname looked private.” It evaluates documented source and routing conditions.
A VM using Private Google Access, a service in a suitable Shared VPC path, an internal Application Load Balancer, and specific Google-managed services can qualify through different mechanisms. Conditions such as project placement, VPC Service Controls, and use of the default URL may matter.
Keep the accepted source classes in infrastructure code and architecture documentation. Recheck the current product definition during access reviews because platform-supported callers evolve.
Design the caller path too
For service-to-service calls, identify how the source resolves and reaches the destination. Direct VPC egress controls outbound traffic from a Cloud Run service. It doesn’t create direct ingress to another Cloud Run service. The request still enters through Cloud Run’s supported ingress path.
Use the destination’s run.app URL with an internal-qualified source path, an internal load balancer, or Private Service Connect according to the network contract. Test from the real caller identity and network, not only from a developer machine with broader access.
DNS is part of the design when private endpoints or load balancers are involved. A private IP that resolves only in one VPC isn’t a complete cross-project access model.
Authenticate every machine caller
Keep Cloud Run Invoker authorization enabled unless anonymous access is explicitly required. Give each calling workload a service account, grant the narrow invoker role, and mint an identity token for the target audience.
Network position says where the request came from. IAM says which workload is making it. Use both.
For human access to internal tools, prefer a controlled identity-aware frontend rather than distributing network access and broad invoker roles. The exact choice depends on whether the users already have a private network path and where session policy belongs.
Remove unintended paths
Audit custom domains, load balancer backends, old revisions, traffic tags, service IAM, organization policies, and DNS records. A service can have a restrictive intended frontend while an older path remains callable.
Test the negative cases:
- unauthenticated request through the intended route
- authenticated request from an unapproved source
- direct request to
run.appfrom the internet - request through each load balancer or private endpoint
- call from every Google-managed service expected to invoke it
- cross-project and Shared VPC caller paths
An access model isn’t complete until the denied paths fail for the reason you intended.
Observe path and identity
Log the authenticated principal, target service and revision, route or frontend where available, and request trace. Alert on denied calls from expected integrations and successful calls through routes that should be unused.
Keep a small access matrix:
caller path ingress mode IAM
scheduler default URL internal invoker SA
employees external LB + IAP internal-and-cloud-balancing user policy
private service internal LB or qualified URL internal invoker SA
internet none denied noneThe values will differ. Writing them down forces the design to stop hiding inside one console dropdown.
Cloud Run supports private services well, but ingress, routing, DNS, caller identity, and IAM must agree. “Internal-only” describes the resulting access model, not one checkbox.
More in this domain: Infrastructure
Browse allHow we decide between Cloud SQL connectors, Auth Proxy, and private IP
Cloud SQL connectors, the Auth Proxy, and private IP are not interchangeable secure connection options. They change identity, routing, deployment shape, and how much network plumbing the team actually owns.
Safe scaling defaults for Cloud Run + Postgres
Cloud Run autoscaling is not a database strategy. Safe defaults keep the application from scaling itself into a Postgres incident before the team understands the workload.
IAM DB auth for Cloud SQL: when it simplifies security and when it complicates delivery
IAM DB auth can reduce password sprawl and make revocation cleaner, but it also turns database access into an identity operating model that depends on disciplined service-account boundaries.
Cloud Run request timeouts don't kill your code (so your architecture has to)
A Cloud Run request timeout ends the request, not necessarily the work. If the operation can outlive its caller, the system needs explicit job semantics instead of hope.
Cloud Run scaling from zero is a feature until it isn't
Scale to zero is a good default for request-driven services, until startup delay, warm-capacity needs, or instance caps turn it into user-visible reliability behavior instead of a pricing feature.
Related patterns
Direct VPC egress vs Serverless VPC Access for Cloud Run: our default
We default to Direct VPC egress for Cloud Run because it is the cleaner networking shape: fewer moving parts, no connector resource, and costs that scale with the service instead of beside it.
GKE Autopilot as the escape hatch from Cloud Run
When Cloud Run stops fitting, the next move is usually GKE Autopilot: more Kubernetes-shaped control without immediately taking on the full burden of Standard clusters.
Why we default to Cloud Run for SME internal platforms
For SME internal platforms, Cloud Run is our default because it covers a large share of useful workload shapes without forcing teams to own cluster operations before they have earned that surface area.
How we treat Terraform state in team environments
Terraform starts feeling fragile in teams when state is treated like a backend setting instead of a shared dependency for safe change.