How can I run Django on a subpath on Google Cloud Run with load balancer?

I'll preface by noting that I have a system set up using Google Cloud Run + Load Balancer + IAP to run a number of apps on,, etc, and up to now I've only deployed Streamlit apps this way. The load balancer is directing traffic to each app in Cloud Run according to subpath (/app1, ...), and I used the --server.baseUrlPath=app2 option of streamlit run with no problems.

Now I'm trying to get a 'hello, world' Django 4.1.5 app running on, and I can't seem to get the routes right trying differing suggestions here, here, and here.

The Dockerfile ends with

CMD exec poetry run gunicorn --bind${PORT} --workers 1 --threads 8 --timeout 0 example_dir.wsgi:application

First, the canonical solution.

I added FORCE_SCRIPT_NAME = "/directory" in
Here's example_dir/

urlpatterns = urlpatterns = [
    path("", include("directory.urls")),

and here's directory/

urlpatterns = [
    path("", views.hello, name="hello"),

Visiting returns

Page not found (404)
Request Method: GET
Request URL:
Using the URLconf defined in example_dir.urls, Django tried these URL patterns, in this order:

1. admin/
2. [name='hello']

That Request URL is surprising and weird.

Adding either USE_X_FORWARDED_HOST = True or SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') or both(per the reference) did not affect the result.

Second, the ugly solution.

If I change example_dir/ to

urlpatterns = [
    path("directory/", include("directory.urls")),

then visiting works properly, but returns but is unstyled:
unstyled django admin page
This is obviously due to the <head> having hrefs like /static/admin/....

Changing STATIC_URL in to "directory/static/" fixed the hrefs but I see several errors on the console like

Refused to apply style from '' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.

An orthogonal variable

I can't really do this in prod, but as an experiment I changed the Docker CMD to

CMD exec poetry run python runserver${PORT}

This does not affect the results of the FORCE_SCRIPT_NAME solution, but it fixes the problem with the ugly solution. Specifically, the MIME type errors are gone and the admin login page looks normal.

I don't think script name has anything to do with what you are trying to achieve. You need to have a WSGI app wrapping your Django app so that you can inject a path before.

There is an example on how to achieve this on Gunicorn

Here is a modified version for your use case

from routes import Mapper
from example_dir.wsgi import application as app1

class Application(object):
    def __init__(self): = Mapper()'app1', '/directory', app=app1)

    def __call__(self, environ, start_response):
        match =
        if not match:
            return self.error404(environ, start_response)
        return match[0]['app'](environ, start_response)

    def error404(self, environ, start_response):
        html = b"""\
            <title>404 - Not Found</title>
            <h1>404 - Not Found</h1>
        headers = [
            ('Content-Type', 'text/html'),
            ('Content-Length', str(len(html)))
        start_response('404 Not Found', headers)
        return [html]

app = Application()
Back to Top