Dash Web Apps

When a Dash web app is run from the command line, a warning is generated that the python development server should not be used in a "production" environment and points to something called WSGI instead.

stuart@pi-zero:~/DS18B20 $ python3 test2.py
Dash is running on http://0.0.0.0:8051/

 * Serving Flask app "test2" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on

My application is not mission critical and could happily run (or not) with the development server but to embed the live temperature plot in an html page with more than just the plot itself requires a modified approach. The first thing I tried was using the exported html embedded as an iframe as shown in the previous post and having the html refresh itself regularly but this was ugly mainly due to the capabilities of the Pi-Zero (more on that later) which meant the refresh was very slow and obvious. The recommended way is to use WSGI which allows Apache to execute python code (this may not be exactly what it is doing but the end result is the same)

Dash App Served From Apache

Apache is not installed by default on the Pi so do the following which installs, starts and enables it.

sudo apt install apache2

Then do the following steps, the sequence does not matter as none of the changes will be active until Apache is restarted at the end.

Create the WSGI file

cd /var/www/html
mkdir plot # I'll use this name for my app, any name is ok but be consistent in later steps
cd plot

then sudo nano plot.wsgi (any name is ok as long as it is kept consistent in the later files) and add the following, ctrl-O, Y, ctrl-X to save and exit.

import sys
sys.path.insert(0,"/var/www/html/plot/")
from plot import server as application

There are lots of help requests and answers on internet for this topic and I looked at many of them 1 2 3 so replacing the last line with the following two lines also works but is unnecessary.

from plot import app
application = app.server

However the following line does not work and gives a 500 internal error with the rest of my config which is further below but the page this came from was for Flask, not Dash, apps which may explain this.

from plot import app as application # this did not work for me

Adjust the python app

The python app should be copied to the same directory as the .wsgi file and some changes are needed within the python app itself. Later we'll see that it is possible to run the python app from its original location in my home directory but I find it tidier to keep things in one place.

app = dash.Dash(__name__)

should have the path to the app added

app = dash.Dash(__name__, requests_pathname_prefix='/plot/')

and server=app.server needs to be added, anywhere is ok but I added it just before the call to the app. As the app is being called by Apache, the if statement ensures that the app does not run standalone which would lead to a port conflict.

server=app.server
if __name__ == '__main__':
    app.run_server(debug=True,host='0.0.0.0', port='8050')

Tell Apache where to find the app

Install an apache module which will be enabled as part of the install but if for any reason it is not, enable it manually

sudo apt install libapache2-mod-wsgi-py3
sudo a2enmod mod-wsgi # expect it to say already enabled

As you can see above this app is running on port 8050 so we'll tell Apache to listen there as well by adding Listen 8050 in the file /etc/apache2/ports.conf directly below Listen 80. Now we need to create a new site

cd /etc/apache2/sites-available
sudo nano plot.conf

In this file we need the following

VirtualHost *:8050>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/html/plot
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
#        WSGIDaemonProcess plot user=www-data group=www-data home=/var/www/html/plot threads=5
        WSGIDaemonProcess plot home=/var/www/html/plot threads=5
        WSGIScriptAlias /plot /var/www/html/plot/plot.wsgi
        <Directory /var/www/html/plot/>
            WSGIProcessGroup plot
                WSGIApplicationGroup %{GLOBAL}
                WSGIScriptReloading On
            Require all granted
        </Directory>
</VirtualHost>

This file generates a lot of different responses in requests for help, I suspect syntax has evolved over time but most of them did not work for me with no clear error message. On the WSGIDaemonProcess line, user= and group= are not needed as the defaults are the Apache user, home= in particular is needed as my app reads the SQLite database in the same directory and it needs to know the working directory otherwise it will default to the apache webroot. Adjusting the parameters on this line would allow the script to run from my home directory under my user name. The documentation 4 for WSGI is excellent and a good starting point as it goes beyond just stating what the options do.

Lastly Apache needs to be updated and restarted. Note that any subsequent changes to the python files will need a restart.

sudo a2dissite 000-default.conf # disable the default site
sudo a2ensite plot.conf # enable the new site
sudo systemctl restart apache2.service # restart apache

At the end the directory structure under the webroot will look like this, the __pycache__ directory is auto generated and the SQLite database file was manually copied to this location:

└── plot
    ├── plot.py
    ├── plot.wsgi
    ├── __pycache__     # this is auto generated
    │   └── plot.cpython-39.pyc # this is auto generated
    └── SQLite-temp_multi_ds18b20.db # this is a copy of the measurement database

Having done all of the above and adjusted it many times based on multiple searches I was not getting any errors but I was also not getting the plot to display at http://pi-IP:8050/plot but a night of sleep led me to check if I was overloading the Pi and by reducing the number of datapoints to 100 (from several thousands) I was asking it to handle it started working so while the Pi Zero can handle the measurement it will not be enough to display them with Apache.

 df = pd.read_sql("select * from temperature order by Date_Time desc limit 100", con=conn)

With this last adjustment everything worked.


  1. https://steviesblog.de/blog/en/2020/09/23/deploy-dash-with-apache2/ 

  2. https://jackhalpinblog.wordpress.com/2016/08/27/getting-your-python-3-flask-app-to-run-on-apache/ 

  3. https://community.plotly.com/t/deploy-dash-on-apache-server-solved/4855/12 

  4. https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIDaemonProcess.html 

Previous Post Next Post