How to create *.cloud.run URLs for Cloud Run services
Every Cloud Run service gets a default .run.app URL that embeds your
project number and region, such as
https://web-123456789012.europe-west1.run.app. That works great, but it's a
bit of a mouthful. Cloud Run now lets you claim a unique
https://<subdomain>.cloud.run URL directly from the
gcloud CLI (in preview as of November 2026).
A
.cloud.run service URL
goes live within a few seconds. Below, I walk through deploying a service with a
.cloud.run URL, disabling the default .run.app address,
handling taken subdomains, and updating or deleting a service with a
.cloud.run subdomain.
Deploy with a .cloud.run URL
To try this out, set your Google Cloud project ID, target region, and desired subdomain
in your shell. Because all .cloud.run subdomains share a single global
namespace across Google Cloud, the tr and head pipeline below
reads /dev/urandom to generate six random lowercase letters and digits
(such as mv72a9). That gives you a unique name you can copy and paste right
away without hitting a collision, though you can set WEB_SUBDOMAIN to any
unclaimed name you like:
export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="europe-west1"
export WEB_SUBDOMAIN="web-$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 6)"
echo "https://$WEB_SUBDOMAIN.cloud.run"
https://web-mv72a9.cloud.run
If you pick your own name, use between 6 and 63 characters with lowercase letters,
numbers, and hyphens, without starting or ending with a hyphen. When I tested different
names, I found that Cloud Run also blocks some terms, such as google,
api, auth, admin, and test (for
example, my-api-mv72a9.cloud.run fails with
contains the prohibited term 'api').
Before deploying, enable the Cloud Run Admin API in your project:
gcloud services enable run.googleapis.com \
--project="$PROJECT_ID"
Operation "operations/acf.p2-123456789012-396be3b7-4aee-40ad-8fc6-ef05544f67bf" finished successfully.
With the API enabled and your variables set, you can deploy a sample NGINX container and
claim $WEB_SUBDOMAIN.cloud.run in a single command. Pass
--domain to gcloud beta run deploy to register the subdomain
and route it to a new web service:
gcloud beta run deploy web \
--image=nginx \
--port=80 \
--no-invoker-iam-check \
--domain="$WEB_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID"
Deploying container to Cloud Run service [web] in project [YOUR_PROJECT_ID] region [europe-west1]
✓ Deploying new service... Done.
✓ Creating Revision...
✓ Creating domain mapping...
✓ Routing traffic...
Done.
Service [web] revision [web-00001-p5d] has been deployed and is serving 100 percent of traffic.
Service URL: https://web-123456789012.europe-west1.run.app
-
--image=nginxand--port=80deploy the official NGINX container image from Docker Hub and route traffic to container port80, where NGINX listens by default, instead of Cloud Run's default port8080. -
--no-invoker-iam-checkdisables Cloud Run's IAM invoker check so the service is publicly available. -
--domain="$WEB_SUBDOMAIN.cloud.run"creates a domain mapping that routes your.cloud.runsubdomain to the serviceweb.
Even though the CLI output only prints the default Service URL, Cloud Run
provisions your .cloud.run route over HTTPS in the background. Routing
takes a few seconds to propagate after the command finishes (requests during that window
return 404). Send a HEAD request with curl to
verify (Cloud Run's front-end proxy overwrites NGINX's server header with
Google Frontend):
curl -I "https://$WEB_SUBDOMAIN.cloud.run"
HTTP/2 200
content-type: text/html
content-length: 896
date: Fri, 06 Nov 2026 10:05:14 GMT
server: Google Frontend
During the deployment, gcloud beta run deploy printed
Creating domain mapping.... Passing --domain creates a
separate domain mapping resource in the region that routes
$WEB_SUBDOMAIN.cloud.run to the Cloud Run service. Run
gcloud beta run domain-mappings list to see the mapping:
gcloud beta run domain-mappings list \
--region="$REGION" \
--project="$PROJECT_ID"
DOMAIN SERVICE REGION
✔ web-mv72a9.cloud.run web europe-west1
Disable the default .run.app URL
Because the .cloud.run domain mapping is separate from the service's
default .run.app endpoint, you can turn off the
.run.app address while keeping
https://<subdomain>.cloud.run live. Save the default URL in
DEFAULT_URL so you can test it in a moment, then pass
--no-default-url to gcloud run services update (or include it
during gcloud beta run deploy):
DEFAULT_URL=$(gcloud run services describe web \
--region="$REGION" \
--project="$PROJECT_ID" \
--format='value(status.url)')
gcloud run services update web \
--no-default-url \
--region="$REGION" \
--project="$PROJECT_ID"
✓ Updating... Done.
Done.
Service [web] has been updated.
Disabling the default URL updates the service configuration without a new deployment.
Verify with curl that the default .run.app endpoint now
returns 404 while your .cloud.run URL continues returning
200:
curl -I "$DEFAULT_URL"
curl -I "https://$WEB_SUBDOMAIN.cloud.run"
HTTP/2 404
date: Fri, 06 Nov 2026 10:06:22 GMT
server: Google Frontend
content-type: text/html; charset=UTF-8
HTTP/2 200
content-type: text/html
content-length: 896
date: Fri, 06 Nov 2026 10:06:23 GMT
server: Google Frontend
What happens when a .cloud.run URL is already taken
Because all .cloud.run URLs share a global pool, someone else might already
have claimed the subdomain you want. Try deploying a new Cloud Run service named
wietse with --no-default-url and
wietse.cloud.run, which I already claimed:
gcloud beta run deploy wietse \
--image=nginx \
--port=80 \
--no-invoker-iam-check \
--no-default-url \
--domain="wietse.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID"
Deploying container to Cloud Run service [wietse] in project [YOUR_PROJECT_ID] region [europe-west1]
✓ Deploying new service...
✓ Creating Revision...
X Creating domain mapping...failed
Deployment failed
ERROR: (gcloud.beta.run.deploy) Domain mapping to [wietse.cloud.run] already exists in this region.
Even though the error message says already exists in this region,
.cloud.run subdomains are reserved globally across all regions, so
switching regions returns the same error. Also notice that
gcloud beta run deploy deploys the service before registering the domain
mapping. Because the command included --no-default-url and the
.cloud.run mapping failed, the wietse service is now running
without any URL at all.
To check whether a .cloud.run subdomain is available and valid for an
existing service without creating the mapping, pass --dry-run to
gcloud beta run domain-mappings create:
export WIETSE_SUBDOMAIN="wietse-$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 6)"
gcloud beta run domain-mappings create \
--service=wietse \
--domain="$WIETSE_SUBDOMAIN.cloud.run" \
--dry-run \
--region="$REGION" \
--project="$PROJECT_ID"
Dry run: domain mapping [wietse-r4t9x1.cloud.run] to service [wietse] is valid and would be created.
Once the dry run succeeds, run the command without --dry-run to map the
subdomain to wietse:
gcloud beta run domain-mappings create \
--service=wietse \
--domain="$WIETSE_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID"
✓ Creating......done.
The wietse service is now live at
https://$WIETSE_SUBDOMAIN.cloud.run.
Clean up the domain mapping when deleting a service
Each domain mapping is a separate resource, and deleting a Cloud Run service doesn't
automatically delete its .cloud.run mapping. Delete the
wietse service to see what happens to
$WIETSE_SUBDOMAIN.cloud.run:
gcloud run services delete wietse \
--region="$REGION" \
--project="$PROJECT_ID" \
--quiet
✓ Deleting [wietse]...done.
Deleted service [wietse].
List your domain mappings again to see that $WIETSE_SUBDOMAIN.cloud.run is
still registered in your project:
gcloud beta run domain-mappings list \
--region="$REGION" \
--project="$PROJECT_ID"
DOMAIN SERVICE REGION
✔ web-mv72a9.cloud.run web europe-west1
X wietse-r4t9x1.cloud.run wietse europe-west1
domain-mappings list marks wietse-r4t9x1.cloud.run with
X because its target service (wietse) no longer exists, and
requests to https://$WIETSE_SUBDOMAIN.cloud.run return 404.
Delete the dangling mapping with gcloud beta run domain-mappings delete so
you release the subdomain (passing --quiet to skip the confirmation
prompt):
gcloud beta run domain-mappings delete \
--domain="$WIETSE_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID" \
--quiet
✓ Deleting [wietse-r4t9x1.cloud.run]...done.
Mappings to [wietse-r4t9x1.cloud.run] now have been deleted.
Update the .cloud.run URL mapped to a service
Because domain mappings are separate from your service deployments, you can change the
.cloud.run URL on a running service without redeploying its container. To
replace $WEB_SUBDOMAIN.cloud.run on the Cloud Run service
web you deployed earlier, create a new mapping with
gcloud beta run domain-mappings create and then delete the old mapping with
gcloud beta run domain-mappings delete:
export NEW_WEB_SUBDOMAIN="web-new-$(LC_ALL=C tr -dc 'a-z0-9' </dev/urandom | head -c 6)"
gcloud beta run domain-mappings create \
--service=web \
--domain="$NEW_WEB_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID"
gcloud beta run domain-mappings delete \
--domain="$WEB_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID" \
--quiet
✓ Creating......done.
✓ Deleting [web-mv72a9.cloud.run]...done.
Mappings to [web-mv72a9.cloud.run] now have been deleted.
You might wonder if redeploying the service with gcloud beta run deploy and
--domain="$NEW_WEB_SUBDOMAIN.cloud.run" would replace the URL in one step.
It doesn't. Instead, it redeploys the service and adds a second domain mapping alongside
the first one, so you still have to run domain-mappings delete to remove
the old subdomain.
Map a .cloud.run URL to a different service
You can't edit an existing domain mapping to change its target service. To point
$NEW_WEB_SUBDOMAIN.cloud.run at a new web-v2 service instead
of web, you have to delete the mapping and recreate it. First, deploy
web-v2:
gcloud run deploy web-v2 \
--image=nginx \
--port=80 \
--no-invoker-iam-check \
--region="$REGION" \
--project="$PROJECT_ID"
Deploying container to Cloud Run service [web-v2] in project [YOUR_PROJECT_ID] region [europe-west1]
✓ Deploying new service... Done.
✓ Creating Revision...
✓ Routing traffic...
Done.
Service [web-v2] revision [web-v2-00001-x9k] has been deployed and is serving 100 percent of traffic.
Service URL: https://web-v2-123456789012.europe-west1.run.app
Next, delete the mapping on web and create a new mapping that points
$NEW_WEB_SUBDOMAIN.cloud.run to web-v2:
gcloud beta run domain-mappings delete \
--domain="$NEW_WEB_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID" \
--quiet
gcloud beta run domain-mappings create \
--service=web-v2 \
--domain="$NEW_WEB_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID"
✓ Deleting [web-new-k8w2p4.cloud.run]...done.
Mappings to [web-new-k8w2p4.cloud.run] now have been deleted.
✓ Creating......done.
Because you have to delete the existing mapping before creating the new one, the
subdomain is momentarily unclaimed during the cutover, and requests can return
404 for a few seconds after domain-mappings create finishes
while routing propagates.
Moving a .cloud.run mapping to another region works the same way: delete
the mapping in europe-west1 and run
domain-mappings create with --region=europe-west4 for your
service in that region.
When you're done experimenting, delete web, web-v2, and the
remaining domain mapping so you don't leave resources or reserved subdomains behind:
gcloud run services delete web \
--region="$REGION" \
--project="$PROJECT_ID" \
--quiet
gcloud run services delete web-v2 \
--region="$REGION" \
--project="$PROJECT_ID" \
--quiet
gcloud beta run domain-mappings delete \
--domain="$NEW_WEB_SUBDOMAIN.cloud.run" \
--region="$REGION" \
--project="$PROJECT_ID" \
--quiet
✓ Deleting [web]...done.
Deleted service [web].
✓ Deleting [web-v2]...done.
Deleted service [web-v2].
✓ Deleting [web-new-k8w2p4.cloud.run]...done.
Mappings to [web-new-k8w2p4.cloud.run] now have been deleted.