Deploying Flask+Bokeh to Heroku

I was trying to deploy Flask web applications with embedded Bokeh served via Gunicorn to Heroku. It project use Tornado HTTP server for asyncio.

Blockquote from threading import Thread
Thread(target=bk_worker).start()

Can somebody help me with it?

Hi @ArtemAleksieiev we would love to help but you will need to provide much more detail for anyone to be able to speculate. Things like:

  • errors in bokeh server logs
  • Heroku proxy logs
  • messages in the JavaScript console
  • detailed configuration of the Bokeh server object
  • description of what actually happens when you try to access

Hello Bryan.
For now I left the idea of deploying to Heroku and trying to deploy on Digital Ocean. I have successfully done with flask+bokeh app as separate files(server_document) but need help with deploying bokeh in flask as a library. I have used this starter kit https://github.com/LunkRat/flask-bokeh-dashboard. App working locally but online loaded only flask, /bkapp gives 502 error and any js error not seen in browser console. Something wrong with ports and I don’t exactly understand how it must be. That what I have:
dashboard.py

  import asyncio
    except ImportError:
        raise RuntimeError("This example requries Python3 / asyncio")
    #################################################################
    if __name__ == '__main__':
        import sys
    bkapp = Application(FunctionHandler(modify_doc))
    sockets, port = bind_sockets("157.245.XX.XXX", 0)
    ###############################################################
       script = server_document(
          '157.245.12.139:%d/bkapp' % port,
          arguments=dict(
          plot_title = plot_title,
          dataset = datasets.get(current_dataset),
          unit_type = unit_type,
          theme = theme)
    ################################################################
    def bk_worker():
        asyncio.set_event_loop(asyncio.new_event_loop())

        bokeh_tornado = BokehTornado({'/bkapp': bkapp}, extra_websocket_origins=[ "157.245.XX.XXX: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()
    from threading import Thread
    Thread(target=bk_worker).start()

gunicorn settings:

[Unit]
Description=Gunicorn instance to serve flask embedded bokeh
After=network.target

[Service]
User=artemski
Group=www-data
WorkingDirectory=/home/artemski/dashboard
Environment="PATH=/home/artemski/envdir/myvenv/bin"
ExecStart= /home/artemski/envdir/myvenv/bin/gunicorn --workers 4 --bind unix:brew.sock -m 007 wsgi:app

[Install]
WantedBy=multi-user.target

nginx settings

server {
    listen 80;
    server_name 157.245.XX.XX;

    access_log  /tmp/bokeh.access.log;
    error_log   /tmp/bokeh.error.log debug;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://unix:/home/artemski/dashboard/brew.sock;
     }
    location /bkapp/ {
        proxy_pass http://127.0.0.1:5100;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_buffering off;
     }
     location /bkapp/static/ {
        alias /home/artemski/dashboard/static/;
     }
}

bokeh error log
2019/09/27 19:49:03 [debug] 20111#20111: *1 write new buf t:1 f:0 0000560579F5DBC0, pos 0000560579F5DBC0, size: 166 file: 0, size: 0
2019/09/27 19:49:03 [debug] 20111#20111: *1 http write filter: l:0 f:0 s:166
2019/09/27 19:49:03 [debug] 20111#20111: *1 http output filter “/bkapp/?”
2019/09/27 19:49:03 [debug] 20111#20111: *1 http copy filter: “/bkapp/?”
2019/09/27 19:49:03 [debug] 20111#20111: *1 image filter
2019/09/27 19:49:03 [debug] 20111#20111: *1 xslt filter body
2019/09/27 19:49:03 [debug] 20111#20111: *1 http postpone filter “/bkapp/?” 0000560579F5DDF0
2019/09/27 19:49:03 [debug] 20111#20111: *1 write old buf t:1 f:0 0000560579F5DBC0, pos 0000560579F5DBC0, size: 166 file: 0, size: 0
2019/09/27 19:49:03 [debug] 20111#20111: *1 write new buf t:0 f:0 0000000000000000, pos 0000560579B66BA0, size: 120 file: 0, size: 0
2019/09/27 19:49:03 [debug] 20111#20111: *1 write new buf t:0 f:0 0000000000000000, pos 0000560579B680A0, size: 62 file: 0, size: 0
2019/09/27 19:49:03 [debug] 20111#20111: *1 write new buf t:0 f:0 0000000000000000, pos 0000560579B67E80, size: 402 file: 0, size: 0
2019/09/27 19:49:03 [debug] 20111#20111: *1 http write filter: l:1 f:0 s:750
2019/09/27 19:49:03 [debug] 20111#20111: *1 http write filter limit 0
2019/09/27 19:49:03 [debug] 20111#20111: *1 writev: 750 of 750
2019/09/27 19:49:03 [debug] 20111#20111: *1 http write filter 0000000000000000
2019/09/27 19:49:03 [debug] 20111#20111: *1 http copy filter: 0 “/bkapp/?”
2019/09/27 19:49:03 [debug] 20111#20111: *1 http finalize request: 0, “/bkapp/?” a:1, c:1
2019/09/27 19:49:03 [debug] 20111#20111: *1 set http keepalive handler
2019/09/27 19:49:03 [debug] 20111#20111: *1 http close request
2019/09/27 19:49:03 [debug] 20111#20111: *1 http log handler
2019/09/27 19:49:03 [debug] 20111#20111: *1 free: 0000560579F70CF0, unused: 8
2019/09/27 19:49:03 [debug] 20111#20111: *1 free: 0000560579F5CFF0, unused: 175
2019/09/27 19:49:03 [debug] 20111#20111: *1 free: 0000560579F123D0
2019/09/27 19:49:03 [debug] 20111#20111: *1 hc free: 0000000000000000
2019/09/27 19:49:03 [debug] 20111#20111: *1 hc busy: 0000000000000000 0
2019/09/27 19:49:03 [debug] 20111#20111: *1 reusable connection: 1
2019/09/27 19:49:03 [debug] 20111#20111: *1 event timer add: 3: 65000:152452091

bokeh access log
24.185.XXX.XXX - - [27/Sep/2019:19:48:53 +0000] “GET / HTTP/1.1” 200 3122 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36”

24.185.XX.XXX - - [27/Sep/2019:19:48:54 +0000] “GET /static/css/lib/material.min.css HTTP/1.1” 200 141212 “http://157.245.XX.XXX/” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36”

24.185.XXX.XXX - - [27/Sep/2019:19:48:54 +0000] “GET /static/css/styles.css HTTP/1.1” 200 4808 “http://157.245.XX.XX/” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36”

24.185.118.XXX - - [27/Sep/2019:19:48:54 +0000] “GET /static/js/lib/material.min.js HTTP/1.1” 200 62491 “http://157.245.XX.XXX/” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36”

24.185.118.XXX - - [27/Sep/2019:19:48:54 +0000] “GET /static/js/lib/material.min.js.map HTTP/1.1” 404 209 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36”

24.185.118.XXX- - [27/Sep/2019:19:49:02 +0000] “GET /bkapp HTTP/1.1” 301 194 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36”

24.185.XXX.XXX - - [27/Sep/2019:19:49:03 +0000] “GET /bkapp/ HTTP/1.1” 502 584 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36”

nginx.service status*

nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2019-09-27 19:48:45 UTC; 43min ago
Docs: man:nginx(8)
Process: 20091 ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid (code=exited, status=0/SUCCESS)
Process: 20104 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 20093 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 20107 (nginx)
Tasks: 2 (limit: 1109)
CGroup: /system.slice/nginx.service
├─20107 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
└─20111 nginx: worker process

Hi @ArtemAleksieiev what would be most useful are actually:

  • The actual application / Bokeh server console logs. The “bokeh error log” above appears to be an nginx log of some sort

  • JS console logs from the browser when a connection is attempted

Hi @Bryan
After I added these lines to flask:
import logging
logging.basicConfig(level=logging.DEBUG)
restarted gunicorn and ngnix, $ systemctl status dashboard
I see this output:
● dashboard.service - Gunicorn instance to serve flask embedded bokeh
Loaded: loaded (/etc/systemd/system/dashboard.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2019-09-28 18:01:02 UTC; 1min 7s ago
Main PID: 28418 (gunicorn)
Tasks: 9 (limit: 1109)
CGroup: /system.slice/dashboard.service
├─28418 /home/artemski/envdir/myvenv/bin/python3 /home/artemski/envdir/myvenv/bin/gunicorn --workers 4 --bind unix:brewasis.sock -m 007 wsgi:app
├─28438 /home/artemski/envdir/myvenv/bin/python3 /home/artemski/envdir/myvenv/bin/gunicorn --workers 4 --bind unix:brewasis.sock -m 007 wsgi:app
├─28441 /home/artemski/envdir/myvenv/bin/python3 /home/artemski/envdir/myvenv/bin/gunicorn --workers 4 --bind unix:brewasis.sock -m 007 wsgi:app
├─28442 /home/artemski/envdir/myvenv/bin/python3 /home/artemski/envdir/myvenv/bin/gunicorn --workers 4 --bind unix:brewasis.sock -m 007 wsgi:app
└─28443 /home/artemski/envdir/myvenv/bin/python3 /home/artemski/envdir/myvenv/bin/gunicorn --workers 4 --bind unix:brewasis.sock -m 007 wsgi:app

Sep 28 18:01:51 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28443] 0 clients connected
Sep 28 18:01:51 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28443]   /bkapp has 0 sessions with 0 unused
Sep 28 18:02:05 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28438] 0 clients connected
Sep 28 18:02:05 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28438]   /bkapp has 0 sessions with 0 unused
Sep 28 18:02:06 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28441] 0 clients connected
Sep 28 18:02:06 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28441]   /bkapp has 0 sessions with 0 unused
Sep 28 18:02:06 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28442] 0 clients connected
Sep 28 18:02:06 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28442]   /bkapp has 0 sessions with 0 unused
Sep 28 18:02:06 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28443] 0 clients connected
Sep 28 18:02:06 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[28418]: DEBUG:bokeh.server.tornado:[pid 28443]   /bkapp has 0 sessions with 0 unused

This is also line from browser html:
script src=“157.245.12.XXX:37183/bkapp/autoload.js?bokeh-autoload-element=1002&bokeh-app-path=/37183/bkapp&bokeh-absolute-url=157.245.12.XXX:37183/bkapp&plot_title=Sea+Surface+Temperature+at+43.18%2C±70.43&dataset=Dataset+One&unit_type=Celcius&theme=default” id=“1002” script

and this page
157.245.12.XXX:37183
gives an error:
GET http://157.245.12.XXX:38813/bkapp 500 (Internal Server Error)

500 error usually means your flask app code is raising an exception somewhere. I still am not seeing what looks like logging from the Boden server in that. And would still be very interested in the browser JS console logs.

Here are some of the kinds of message that will be in the debugging log output that I am asking about:

2019-09-28 11:54:09,800 These host origins can connect to the websocket: ['localhost:5006']
2019-09-28 11:54:09,801 Patterns are:
2019-09-28 11:54:09,801   [('/sliders/?',
2019-09-28 11:54:09,801     <class 'bokeh.server.views.doc_handler.DocHandler'>,
2019-09-28 11:54:09,801     {'application_context': <bokeh.server.contexts.ApplicationContext object at 0x1130e4990>,

Whatever logs have that, those are the logs I am insterested in, specifically error messages. Otherwise, it’s possible that the problem lies elsewhere outside the Bokeh app part of the Flask app.

And to be clear: the browser JS console logs are accessed via your browsers debugging tools, which varies by browser. You will have to search for how to see them on whatever browser you are using.

You might also try simplifying things. Is there a problem still if you the use Flask app directly without gunicorn involved? That might be informative, or clear away unrelated irrelevant signals…

Thank you for example.
After restart app I find these:

Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: {‘application_context’: <bokeh.server.contexts.ApplicationContext object at 0x7f8420296b10>,
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: ‘bokeh_websocket_path’: ‘/bkapp/ws’}),
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: (‘/?’,
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: <class ‘bokeh.server.views.root_handler.RootHandler’>,
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: {‘applications’: {‘/bkapp’: <bokeh.server.contexts.ApplicationContext object at 0x7f8420296b10>},
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: ‘index’: None,
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: ‘prefix’: ‘’,
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: ‘use_redirect’: True}),
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: (‘/static/(.*)’,
Sep 28 19:17:39 ubuntu-s-1vcpu-1gb-nyc3-01 gunicorn[29167]: DEBUG:bokeh.server.tornado: <class ‘bokeh.server.views.static_handler.StaticHandler’>)]

Now will try set environment variable.

when I start app locally at http://127.0.0.1:8000 and go to port http://127.0.0.1:56462 this line from my example raise error 500:

File “/Users/artemaleksieiev/bokeh/bokeh_app3/main.py”, line 120, in modify_doc
plot_title = flask_args.get(‘plot_title’)[0].decode(“utf-8”)
TypeError: ‘NoneType’ object is not subscriptable

because don’t have flask_args. Main page work perfect.
Can this be a problem for deploying app to digitalocean?

@ArtemAleksieiev I am afraid I don’t really understand what you are trying to accomplish. Are you trying to set the plot title from an HTTP request argument like ?plot_title=foo ? If so, when you embed a Bokeh server like this, the URL of the embedding (Flask) app, and the URL for the embedded Bokeh server are not related or linked. If you want HTTP args from the Flask app to make it it to the Bokeh server, you will have to explicitly copy them over in your call to server_document (there’s a parameter to pass HTTP arguments) and then access them as described here:

Running a Bokeh server — Bokeh 2.4.2 Documentation

Hi, @Bryan,
After added try-except block for every argument it works beautiful! Thank you!!

1 Like