Multiapp bokeh server with docker empty response

I have a bokeh server dashboard with 15 or so endpoints that all reuse the same plotting function with different data. It is currently a flask webpage with the bokeh server embedded into it (though not married to that, could drop flask and just do bokeh maybe if flask is the root of this, but I don’t think it is). I want to put it into a docker container now, but when I do, and navigate in my browser to my endpoints, the page loads, but it doesn’t load the bokeh figures. The developer tools gives me “net::ERR_EMPTY_RESPONSE”. I am using the flask_gunicorn_embed.py example from the bokeh docs as my a base, and that dynamically maps the ports to whatever is an open port, since it doesn’t know how many ports you will need, depending on how many endpoints and such. Which I think is my problem. On the outside of the container the browser is looking for 127.0.0.1/12345/app_name, which is empty, since that is on the inside of the container.

Is there an easy fix to this? Should I just map each port manually? That won’t impact multiple users accessing the same endpoints right?

Here are some code examples. I would have made a gist, but I got a new phone and lost my 1 factor auth, and it says it will take 4-5 days for them to get me reauthorized.

app/main.py

try:
    import asyncio
except ImportError:
    raise RuntimeError("This example requries Python3 / asyncio")

from threading import Thread

from flask import Flask, render_template, Blueprint
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
from bokeh.embed import server_document
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.server.server import BaseServer
from bokeh.server.tornado import BokehTornado
from bokeh.server.util import bind_sockets


app = Flask(__name__)


def bkapp(doc):
    plot = figure()
    plot.line([1, 2, 3], [3, 2, 1])

    doc.add_root(column(plot))

@app.route('/', methods=['GET'])
def bkapp_page():
    name = bkapp.__name__
    port = start_bk_worker(bkapp, name)
    script = server_document(f'http://localhost:{port}/{name}')
    return render_template("embed.html", script=script)


def start_bk_worker(bokeh_app, app_name):
    sockets, port, app, = _process_bokeh_app(bokeh_app)
    print(f"sockets {sockets} port {port} app {app}")
    t = Thread(target=_bk_worker, kwargs=dict(sockets=sockets, app=app, app_name=app_name))
    t.daemon = True
    t.start()
    return port


def _process_bokeh_app(bokeh_app):
    app = Application(FunctionHandler(bokeh_app))
    sockets, port = bind_sockets("localhost", 0)
    return sockets, port, app


def _bk_worker(sockets, app, app_name):
    asyncio.set_event_loop(asyncio.new_event_loop())

    bokeh_tornado = BokehTornado(
        {f"/{app_name}": app}, extra_websocket_origins=["127.0.0.1:5000", "localhost:5000"])
    bokeh_http = HTTPServer(bokeh_tornado)
    bokeh_http.add_sockets(sockets)

    server = BaseServer(IOLoop.current(), bokeh_tornado, bokeh_http)
    server.start()
    server.io_loop.start()

app/templates/embed.html

<!doctype html>
<html lang="en">
<body>
  {{ script|safe }}
</body>
</html>

requirements.txt

bokeh
Flask

main.py

from app.main import app

if __name__ == '__main__':
    app.run()

Dockerfile

FROM python:3.8-slim-buster

RUN adduser bokeh

# Install dependencies
RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y gcc libpq-dev python3-dev

WORKDIR /home/bokeh


# Setup Python env
COPY requirements.txt requirements.txt
RUN python -m venv venv
RUN pip install -r requirements.txt
RUN pip install gunicorn

# Copy the application over
COPY app app
COPY *.py entrypoint.sh ./
RUN chmod +x entrypoint.sh

# Setup the framework and run it
ENV FLASK_APP main.py

RUN chown -R bokeh ./
USER bokeh

EXPOSE 5000
ENTRYPOINT ["./entrypoint.sh"]

entrypoint.sh

#!/bin/bash
exec gunicorn --bind 0.0.0.0:5000 --access-logfile - --error-logfile - --log-level "INFO" --forwarded-allow-ips="*" main:app

then run on command line
docker build -t test:latest .
docker run -p 5000:5000 test:latest