Panel's default index_html causes prefix confusion behind reverse proxy

We have found that ‘prefix’ in panel.serve causes problems when trying to run apps at a subfolder URL behind a reverse proxy. Or it is possible there is some extra configuration in the Bokeh server that I haven’t been able to find…

Let’s say I have a to run a simple Panel server:

import panel as pn

def app1():
    return pn.pane.Markdown("# Hello From App1")

def app2():
    return pn.pane.Markdown("# Hello From App2")

    "app1": app1, "app2": app2


opts = {
    'port': 5006,
    'title': "Example",
    'websocket_origin': ['localhost:8888', 'localhost:5006'],
    'show': False,
    'index': None

pn.serve(ROUTES, **opts)

If I run this with python3 then I can access the apps fine at localhost:5006/ as expected, including the default Panel index_html page, linking to /app1 and /app2:

The problem I’m about to describe was encountered within a JupyterHub environment where each user’s server is at a sub-folder URL, so we can borrow some components from JupyterHub to set up a simple reverse proxy.

pip install jhsingle-native-proxy==0.3.1
jhsingle-native-proxy --port=8888 --destport=5006 --authtype=none python3

If I access localhost:8888/subfolder/ this seems to work, except that the index page links app1 to the URL /app1. This is incorrect, as localhost:8888/subfolder/app1 is the correct user-facing URL to the sub-app. If I access /subfolder/app1 directly, that works fine.

So maybe I’m supposed to supply a prefix to panel.serve, adding the following just before pn.serve:

opts['prefix'] =  '/subfolder'

But now the index page appears at localhost:8888/subfolder/subfolder/ and the sub-app at localhost:8888/subfolder/subfolder/app1. The index page itself still doesn’t link to a working app - it links to {{ prefix }}/app1, i.e. /subfolder/app1 which isn’t where the app is served since prefix is added both to the server-side routes AND to the user-facing HTML.

The index_html template could be fixed by changing to relative URLs (’.’ instead of ‘{{ prefix }}’ in the template), although I don’t know if that could upset anything else.

Or is there some server configuration that allows me to separate out server-side and user-facing prefixes?

For example in Voila, I can run it like this:

voila file.ipynb --Voila.base_url="/subfolder" --Voila.server_url="/"

meaning that the server itself should serve at the root URL ‘/’ as normal, but any user-facing URLs, e.g. within HTML, should be written with the prefix /subfolder.

This example has emerged from @Marc experimenting with deployment of Panel through ContainDS Dashboards, a method of leveraging your JupyterHub to serve non-Jupyter web apps to authenticated users on your JupyterHub. The GitHub issue is here.