Monthly Archives: July 2021

Find your website’s URL on Cloud Run

If you have a Python app on Google Cloud Run, how can your app determine its own website URL?

When you deploy to Cloud Run, you specify a service name, and every app deployed to Cloud Run gets a unique URL. The domain in the URLs look something like "my-foo-service-8oafjf26aq-uc.a.run.app". That part in the middle is weird ("8oafjf26aq" in my example), and until you have deployed your first service in a project, it is not obvious how to determine what your app’s domain will be.

Here’s one way for your app to discover its own URL after it is deployed, saving you having to hard-code the value somewhere:

# Tested with Python 3.7.
import os

# Requires google-api-python-client google-auth
import googleapiclient.discovery
import google.auth
import google.auth.exceptions

def get_project_id():
    """Find the GCP project ID when running on Cloud Run."""
    try:
        _, project_id = google.auth.default()
    except google.auth.exceptions.DefaultCredentialsError:
        # Probably running a local development server.
        project_id = os.environ.get('GOOGLE_CLOUD_PROJECT', 'development')

    return project_id

def get_service_url():
    """Return the URL for this service, depending on the environment.

    For local development, this will be http://localhost:8080/. On Cloud Run
    this is https://{service}-{hash}-{region}.a.run.app.
    """
    # https://cloud.google.com/run/docs/reference/rest/v1/namespaces.services/list
    try:
        service = googleapiclient.discovery.build('run', 'v1')
    except google.auth.exceptions.DefaultCredentialsError:
        # Probably running the local development server.
        port = os.environ.get('PORT', '8080')
        url = f'http://localhost:{port}'
    else:
        # https://cloud.google.com/run/docs/reference/container-contract
        k_service = os.environ['K_SERVICE']
        project_id = get_project_id()
        parent = f'namespaces/{project_id}'

        # The global end-point only supports list methods, so you can't use
        # namespaces.services/get unless you know what region to use.
        request = service.namespaces().services().list(parent=parent)
        response = request.execute()

        for item in response['items']:
            if item['metadata']['name'] == k_service:
                url = item['status']['url']
                break
        else:
            raise EnvironmentError('Cannot determine service URL')

    return url

This code uses the metadata service to find the Google Cloud Platform project ID, and the K_SERVICE environment variable to find the Cloud Run service name. With that, it makes a request to the namespaces.services.list API, which returns a list of all the services deployed in a project. Looping through the list, it finds the matching service definition, and returns the URL for the service.

Maybe there’s a simpler approach, because this is a bunch of code that one really shouldn’t need to write. I wish Cloud Run would expose the same sort of environment variables that App Engine provides.