Black Lives Matter. Please consider donating to Black Girls Code today.

DASH Deployment: HELP!

I need some help.

Since my first Medium article about DASH (no help there), I’ve been working on part #2 where I automate data collection and then serve up the DASH app on an Ubuntu server. I understand that if I was an Enterprise customer of DASH they’d take care of this stuff for me. I also understand that deploying as a Heroku app works. But what if I don’t want to wait a minute or so every time a Heroku app complies. Or one from GC?

My point is that it has been very difficult for me to deploy my DASH app on a Linux machine and it seems many share the same frustrations. (I have been able to do it, but it has issues).

The beauty of DASH is that it allows me to work in Python and not have to get my hands dirty with Javascript. GREAT! But it doesn’t matter how powerful the DASH app is if I have to be a Linux server expert to get it deployed and useful to others. I understand web servers are a difficult technology. But that doesn’t mean DASH couldn’t have some tutorials on the major cloud providers: AWS, Azure, GC, etc. To expect open-source efforts to have to buy into a Heroku app sort of defeats the idea of open-source. In the end, if it’s a choice between becoming a Linux server expert and ditching DASH for other library (and their Javascript headaches but ample people to help) then, well, I’m struggling here.

I’m off to write my 2nd Medium article. I fear it will be my last on DASH unless someone at DASH can help me out with some questions or improve DASH’s deployment documentation. THANKS!

I have deployed multiple Dash apps on our own servers using Docker and gunicorn, which was pretty straightforward. Would such a solution satisfy you needs, or do you prefer external hosting?

Hi Emil, thanks for the reply. I vaguely know what docker is, but I have enough spinning plates in the air for now! Here are some questions:

o. Why does DASH embed flask? And if it must, to serve pages, why didn’t it embed gunicorn instead because it says flask should only be used to developing? If it has an option to embed gunicorn, then wouldn’t that make deployment a snap?

o. Okay, for whatever reason, you need a production server for DASH, and must use gunicorn or waitress, whatever, why are their different solutions. Some use a WSGI.py module, others call the main .py app directly. What are the benefits or drawbacks of each. Why does they need to reference app:server and not just the app in the py file?

o. Here are some variation, so you can see how unclear I am. Also, why link dash to an import flask or use the internal. What the difference. I general, there is little in the DASH documentations that explains what’s going on in the background. So it all makes me NOT CONFIDENT in what I’m doing.

# Create our DASH app object!
#app = dash.Dash(__name__, external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css'])
# THIS IS FOR GUNICORN (in production server) TO HOOK INTO!
#server = app.server

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
#server = flask.Flask(__name__) # define flask app.server
app = dash.Dash(__name__, external_stylesheets=external_stylesheets) # , server=server call flask server
server = app.server

Right now, my app is working on a micro instance through gunicorn. But it will only serve one person at a time. And for that one person, it won’t update with new data being added to the real-time database. I’m sure there’s a good reason for it, but I have no idea. I’ve read countless things, watched videos.

I’d love an explanation of what’s going on between DASH’s flask and the web server. I’d love to see more guides to basic deployments on a commodity linux server, or any server.

Here’s my webapp.service, which I use from systemctl to start as a service, in linux:

[Unit]
Description=Dash from gunicorn
After=network.target

[Service]
User=maxrottersman
WorkingDirectory=/home/maxrottersman/python_chart_automation
ExecStart=/home/maxrottersman/python_chart_automation/venv/bin/gunicorn -b 0.0.0.0:8000 webapp:server
Restart=always

[Install]
WantedBy=multi-user.target

Here’s the github repo I’ve been making changes on the server, but eventually, they’ll both equal each other. I hope once I finish this, others can use the readme to help them deploy DASH more confidently than I have been able :wink:

You seem to be confused as to what these different pieces of software are and do:

Flask (https://palletsprojects.com/p/flask/) is a web applications framework (literally a framework for building web applications), it lets you build web applications using simple syntax to create responses to routes using the WSGI standard. Flask is used to create the Python implementation of Dash and it’s ability to act as a standard Python web app.

Flask is not a web server, it does not serve pages. It does come with a simple web server so that development and debugging can be done locally, but this web server is not performant and not security tested.

Gunicorn (https://gunicorn.org/) is a web server, it can take web applications that provide routes via the WSGI standard and serve those routes as web pages. It is designed to be performant and secure for serving interactive web applications. It is far from the only web server that you could use with a Flask based web app, there are dozens.

Gunicorn is not designed to be a web application framework, it would not be easy to make web apps using Gunicorn directly (at least the last time I looked in to this).

Different people have different needs and objectives, so multiple pieces are software are written over time to respond to those needs. Just like there are multiple browsers and multiple operating systems there are multiple web servers that serve web apps built on the WSGI standard. Maybe one of those servers only works on their OS, maybe they already have that server configured for other projects, maybe they have very specific performance requirements. Google around and you will find lots of people discussing different WSGI standards, e.g. https://www.appdynamics.com/blog/engineering/an-introduction-to-python-wsgi-servers-part-1/

You can use the web server that comes with Flask but just be aware it is not performant and not secure.

Because that’s the object in the dash framework that provides the WSGI routes for the web server. In the Dash framework the “app” object is typed the Dash object and it has a “server” property that is the underlying Flask app that WSGI servers can use.

That’s just how plotly developed Dash, it’s fairly fundamental to how the Python version of Dash works. By using Flask to build a web application it’s a lot easier than writing your own from scratch. In the same vein of logic they use Python to write Dash rather than developing their own programming language first.

You should share some code / set-up and ask in this community forum, it sounds like you have built your app on global objects and they are locked at any given time or you’re not using the Intervalcomponent to update your graphs / data correctly.

I would highly recommend making a small web application outside Dash by just using Flask. Get a feel for it and learn how it combines with web servers, this will give you a lot more confidence on how Dash is implemented.

I have done that. I am VERY confident about setting up dash on my local machine. Setting it up on a cloud server where it is harder to work with, and will read a real-time updating database, that has been another thing entirely!

That said, THANKS for your all your notes. VERY HELPFUL. I’m going for a walk but will look at the global variable. I do believe you should put some of what you wrote above in the documentation. Reading other posts many people have been confused by the server = app.server line. Seem’s you’re saying the flask is a test appendage, but the real work of the dash app is served (the routes) through its server() function.

I agree I have seen many people be confused about what app.server is. So to be clear in the current Python version of Dash app.server is the Flask app object as listed here: https://flask.palletsprojects.com/en/1.1.x/quickstart/#a-minimal-application

I recommend you try making a simple Flask app and trying to serve that on the cloud, there are more help articles on the Internet about how to do that. Once you can do that you can replace your Flask app with a Dash app and replace where you need app with app:server.

Here’s the question I think I and others struggle with.

  1. Why would one ever use a WSGI.py file with a DASH app? (Thanks again for your time).
  2. If i’m going serve my DASH app to gunicorn, then is should I put the host=‘0.0.0.0’ in the DASH app call or in Gunicorn? You can see how all these many Linux ways of doing this is confusing. NOT YOUR FAULT. Again, just looking for best practices for a basic DASH deploy
  3. Are there any others tips you have about working with gunicorn and DASH? Is my app.service file good for running a DASH app as a service on Ubuntu?

Thanks again in advance for your answers!

NOTE: I removed the GLOBAL statement from some of my database objects. The site is much faster now and I can open in two browsers. However, it’s still a bit sluggish so I may have to report that a Google micro instance is really too underpowered to be much better than DASH on flask :wink: The main thing is you’ve helped me fee more confident!!!

Why would one ever use a WSGI.py file with a DASH app? (Thanks again for your time).

That depends entirely on which type of setup that you choose. In the setup that i am currently using, a such file is not needed.

If i’m going serve my DASH app to gunicorn, then is should I put the host=‘0.0.0.0’ in the DASH app call or in Gunicorn? You can see how all these many Linux ways of doing this is confusing. NOT YOUR FAULT. Again, just looking for best practices for a basic DASH deploy

I guess that you refer to this part of the code,

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

Note that the server is only run, when you execute the file directly (that what the if statement means), i.e. not when the app is served by e.g. gunicorn. Hence the arguments in you app.py file applies to your local debugging configuration, while the arguments to gunicorn applies to your production environment.

Are there any others tips you have about working with gunicorn and DASH? Is my app.service file good for running a DASH app as a service on Ubuntu?

Personally, i prefer to wrap applications in a Docker container rather than running them as a service, but other people might disagree. In the end, it is a matter of what you need (and what you prefer). If you need redundancy and/or to server a large number of clients, Docker containers can be scaled using e.g. Kubernetes.

I removed the GLOBAL statement from some of my database objects.

I don’t think that @Damian was referring to use of the global keyword, but rather you might have stored variables in global scope (which it sounds like you are doing, but we cannot know for sure without seeing some code). While you can normally use the global scope in Python as you like, it is strongly discouraged to use it for storing anything mutable (such as the information from you DB) in Dash.

The reason is, that since the global scope is shared between clients, you can end up with weird behavior if more than client accesses the application. Mutable data should be stored elsewhere, e.g. in Dash store objects or a in a server side cache.

Thanks! I will look at docker! I believe if the following was used in the beginning of the DASH tutorials a lot of pain and suffering could have be avoided. I understand DASH is trying to keep things simple, but I’m not alone in spending a lot of time in this confusion.

# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server # hook for a production web service

app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),

    html.Div(children='''
        Dash: A web application framework for Python.
    '''),

    ...
])

# This is meant only to fire in development if THIS module is run by itself
# In most deployments this module will be imported, like into a "wsgi.py"
# So this function will never run.  Instead, the calling module will
# attach to the server object in server = app.server aboe
if __name__ == '__main__':
    app.run_server(debug=True)

Further, i’d argue that the first paragraph of the “Deployment” section compounds this misunderstanding:

By default, Dash apps run on localhost - you can only access them on your own machine. To share a Dash app, you need to “deploy” your Dash app to a server and open up the server’s firewall to the public or to a restricted set of IP addresses.

By default, DASH apps aren’t meant to run anything is my understanding. The flask function in DASH, from what I understand you saying above, is meant primarily for routing, NOT for running a server. That’s just in there for convenience? I believe this would reduce the confusion I and others have had:

Dash apps expose a server hook to the users prefered production server, like gunicorn, usually through a statement like “server = app.server”. For convenience, Dash provides a localhost server which can be used in development.

Of course, it needs to be worked on, but you get the idea. I get why DASH has taken the approach it has. The goal is to get developer comfortable with Dash as soon as possible. However, the approach has a cost, later, in deployment. If, for example, Dash didnt’ have an embedded server, and the module always had to be called by a wsgi.py type file, I wouldn’t be here writing this.

I hope this can help Dash improve its documentation if it feels these issues are valid.

all of your feedback is great and much appreciated. we will be keeping all of your comments in mind during our next documentation sprints. in the meantime, thanks to everyone in the community forum for helping fill in the gaps!

3 Likes

@maxrottersman

Try this dockerfile for your dash apps:

# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.7-alpine

# Copy local code to the container image.
ENV APP_HOME /application
WORKDIR $APP_HOME
COPY . ./

RUN pip install -r requirements.txt --no-cache-dir 

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
ENTRYPOINT exec gunicorn --bind :$PORT --workers 1 --threads 8 app:app 

Title this Dockerfile and you can run this via Google Cloud Run and they’ll give you ~ $300 in spend.

This assumes your repo (or project directory) looks like this:

my-project/application
# Dockerfile location
my-project/application/Dockerfile
# app.py location
my-project/application/app.py
# [for later] your cloudbuild.yaml - see link above and docs
my-project/cloudbuild.yaml

I have all of my repos setup to automatically build and deploy based on branch naming for feature/stable/prod.

Note: you may need to adjust default memory on the container in Cloud Run depending on your workload/application. Also, this assumes you do not use local state in your application, i.e. remote databases or caching.

Hope this helps!

1 Like

Thanks for taking the time to post this! It will help at some point!