Failed to load resource: net::ERR_CONNECTION_REFUSED -- Flask+Gunicorn+Panel+Docker

Is there a recent example of embedding bokeh/panel onto a flask app and linking with gunicorn. Or anyone who can help be figure out what needs changing in the below example? I’m trying out the embed_flask_gunicorn example but i get a err connection refused.

my code is as follows:

import asyncio
from threading import Thread

from flask import Flask, render_template
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.models import ColumnDataSource, Slider
from bokeh.plotting import figure
from bokeh.sampledata.sea_surface_temperature import sea_surface_temperature
from bokeh.server.server import BaseServer
from bokeh.server.tornado import BokehTornado
from bokeh.server.util import bind_sockets
from bokeh.themes import Theme

if __name__ == '__main__':
    print('This script is intended to be run with gunicorn. e.g.')
    print()
    print('    gunicorn -w 4 flask_gunicorn_embed:app')
    print()
    print('will start the app on four processes')
    import sys
    sys.exit()


app = Flask(__name__)

def bkapp(doc):
    df = sea_surface_temperature.copy()
    source = ColumnDataSource(data=df)

    plot = figure(x_axis_type='datetime', y_range=(0, 25), y_axis_label='Temperature (Celsius)',
                  title="Sea Surface Temperature at 43.18, -70.43")
    plot.line('time', 'temperature', source=source)

    def callback(attr, old, new):
        if new == 0:
            data = df
        else:
            data = df.rolling(f"{new}D").mean()
        source.data = ColumnDataSource.from_df(data)

    slider = Slider(start=0, end=30, value=0, step=1, title="Smoothing by N Days")
    slider.on_change('value', callback)

    doc.add_root(column(slider, plot))

    doc.theme = Theme(filename="theme.yaml")

# can't use shortcuts here, since we are passing to low level BokehTornado
bkapp = Application(FunctionHandler(bkapp))

# This is so that if this app is run using something like "gunicorn -w 4" then
# each process will listen on its own port
sockets, port = bind_sockets("localhost", 0)

@app.route('/', methods=['GET'])
def bkapp_page():
    script = server_document('http://localhost:%d/bkapp' % port)
    return render_template("embed.html", script=script, template="Flask")

def bk_worker():
    asyncio.set_event_loop(asyncio.new_event_loop())

    bokeh_tornado = BokehTornado({'/bkapp': bkapp}, extra_websocket_origins=["localhost:8000"])
    bokeh_http = HTTPServer(bokeh_tornado)
    bokeh_http.add_sockets(sockets)

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

t = Thread(target=bk_worker)
t.daemon = True
t.start()

My dockerfile is as follows:

FROM python:3.8

WORKDIR /app
COPY . .

EXPOSE 8000

RUN pip install -r requirements.txt
CMD gunicorn --bind 0.0.0.0:8000 app:app

My docker compose is as follows:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000"

I try to compose the container as follows:
docker-compose up --scale app=3 --build

My embed.html has a blank body plus the script tag as per jinja templating.

The container builds but localhost: returns a blank page and javascript console returns net::ERR_CONNECTION_REFUSED. This is on my local windows machine. This is the official example so i expected it to work but it’s not. I’m getting the same result when i substitute the panel part with my own code (i.e. unitl the ‘plot’ is created).

I’m trying to implement my panel app on AWS but i want to make sure i don’t get lags in processing if a few people are visiting the app (as warned by a few questions on this forum and stack overflow), hence my opting for the gunicorn approach to route users to ‘instances’ on the fly. Any advice will be appreciated.

My versions are as follows:
bokeh==2.4.3
holoviews==1.15.0
panel==0.13.1

If i can get past this, Panel/Bokeh are just awesome!

It works if you run it according to the instructions in its docstring (I just checked) The complication here is Docker and only having a single port open. You can examine the initial page that is loaded from gunicorn and see that it makes further loads from the bokeh server instances, which are on their own (different) ports:

    xhr.open('GET', "http://localhost:60117/bkapp/autoload.js?bokeh-autoload-element=1001&bokeh-app-path=/bkapp&bokeh-absolute-url=http://localhost:60117/bkapp", true);

This is stated in a comment inside the example:

# This is so that if this app is run using something like "gunicorn -w 4"
# then each process will listen on its own port
sockets, port = bind_sockets("localhost", 0)

The example is configured to use random open ports (the “0” in the call to bind_sockets) Presumably you will need to exert more direct control over the Bokeh server app ports so that you can expose them on the Docker instance.