Since I came to Warwick, I've been working extensively on the LMFDB, which uses python, sage, flask, and mongodb at its core. Thus I've become very familiar with flask. Writing a simple flask application is very quick and easy. So I thought it would be a good idea to figure out how to deploy a flask app on the server which runs this website, which is currently at WebFaction.
In short, it was not too hard, and now the app is set up for use. (It's not a public tool, so I won't link to it).
But there were a few things that I had to think figure out which I would quickly forget. Following the variety of information I found online, the only nontrivial aspect was configuring the site to run on a non-root domain (like davidlowryduda.com/subdomain
instead of at davidlowryduda.com
). I'm writing this so as to not need to figure this out when I write and hoost more flask apps. (Which I'll almost certainly do, as it's so straightforward).
There are some uninteresting things one must do on WebFaction.
- Log into your account.
- Add a new application of type
mod_wsgi
(and the desired version of python, which is hopefully 3.6+). - Add this application to the desired website and subdomain in the WebFaction control panel.
After this, WebFaction will set up a skeleton "Hello World" mod_wsgi application with many reasonable server setting defaults. The remainder of the setup is done on the server itself.
In ~/webapps/application_name
there will now appear
apache2/ # Apache config files and bin
htdocs/ # Default location where Apache looks for the app
We won't change that structure. In htdocs1
1I think htdocs is so named because it once stood for HyperText DOCuments. A bit of trivia.
there is a file index.py
, which is where apache expects to find a python wsgi application called application
. We will place the flask app along this structure and point to it in htdocs/index.py
.
Usually I will use a virtualenv here. So in ~/webapps/application_name
, I will run something like virtualenv flask_app_venv
and virtualenv activate
(or actually out of habit I frequently source the flask_app_venv/bin/activate
file). Then pip install flask and whatever other python modules are necessary for the application to run. We will configure the server to use this virtual environment to run the app in a moment.
Copy the flask app, so that the resulting structure looks something like
~/webapps/application_name:
- apache2/
- htdocs/
- flask_app_venv/
- flask_app/ # My flask app
- config.py
- libs/
- main/
- static/
- templates/
- __init__.py
- views.py
- models.my
I find it conceptually easiest if I have flask_app/main/init.py
to directly contain the flask app
to reference it by name in htdocs/index.py
. It can be made elsewhere (for instance, perhaps in a file like flask_app/main/app.py
, which appears to be a common structure), but I assume that it is at least imported in init.py
.
For example, init.py
might look something like
# application_name/flask_app/main/__init__.py
# ... other import statements from project if necessary
from flask import Flask
app = Flask(__name__)
app.config.from_object('config')
# Importing the views for the rest of our site
# We do this here to avoid circular imports
# Note that I call it "main" where many call it "app"
from main import views
if __name__ == '__main__':
app.run()
The Flask constructor returns exactly the sort of wsgi application that apache expects. With this structure, we can edit the htdocs/index.py
file to look like
# application_name/htdocs/index.py
import sys
# append flask project files
sys.path.append('/home/username/webapps/application_name/my_flask_app/')
# launching our app
from main import app as application
Now the server knows the correct wsgi_application to serve.
We must configure it to use our python virtual environment (and we'll add a few additional convenience pieces). We edit /apache2/conf/httpd.conf
as follows. Near the top of the file, certain modules are loaded. Add in the alias module, so that the modules look something like
#... other modules
LoadModule wsgi_module modules/mod_wsgi.so
LoadModule alias_module modules/mod_alias.so # <-- added
This allows us to alias the root of the site. Since all site functionality is routed through htdocs/index.py
, we want to think of the root /
as beginning with /htdocs/index.py
. At the end of the file
Alias / /home/username/webapps/application_name/htdocs/index.py/
We now set the virtual environment to be used properly. There will be a set of lines containing names like WSGIDaemonProcess
and WSGIProcessGroup
. We edit these to refer to the correct python. WebFaction will have configured WSGIDaemonProcess
to point to a local version of python by setting the python-path. Remove that, making that line look like
WSGIDaemonProcess application_name processes=2 threads=12
(or similar). We set the python path below, adding the line
WSGIPythonHome /home/username/webapps/application_name/flask_app_venv
I believe that this could also actually be done by setting puthon-path in WSGIDaemonProcess, but I find this more aesthetically pleasing.
We must also modify the section. Edit it to look like
<Directory /home/username/webapps/application_name/htdocs>
AddHandler wsgi-script .py
RewriteEngine On # <-- added
RewriteBase / # <-- added
WSGIScriptReloading On # <-- added
<Directory>
It may very well be that I don't use the RewriteEngine at all, but if I do then this is where it's done. Script reloading is a nice convenience, especially while reloading and changing the app.
I note that it may be convenient to add an additional alias for static file hosting,
Alias /static/ /home/your_username/webapps/application_name/app/main/static/
though I have not used this so far. (I get the same functionality through controlling the flask views appropriately).
The rest of this file has been setup by WebFaction for us upon creating the wsgi application.
If the application is on a non-root domain...
If the application is to be run on a non-root domain, such as davidlowryduda.com/subdomain
, then there is currently a problem. In flask, when using url getters like url_for
, urls will be returned as though there is no subdomain. And thus all urls will be incorrect. It is necessary to alter provided urls in some way.
The way that worked for me was to insert a tiny bit of middleware in the wsgi_application. Alter htdocs/index.py
to read
#application_name/htdocs/index.py
import sys
# append flask project files
sys.path.append('/home/username/webapps/application_name/my_flask_app/')
# subdomain url rerouting middleware
from webfaction_middleware import Middleware
from main import app
# set app through middleware
application = Middleware(app)
Now of course we need to write this middleware.
In application_name/flask_app
, I create a file called webfaction_middleware.py
, which reads
# application_name/flask_app/webfaction_middleware.py
class Middleware(object): # python2 aware
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
app_url = '/subdomain'
if app_url != '/':
environ['SCRIPT_NAME'] = app_url
return self.app(environ, start_response)
I now have a template file in which I keep app_url = '/'
so that I can forget this and not worry, but that is where the subdomain url is prepended. Note that the leading slash is necessary. When I first tried using this, I omitted the leading slash. The application worked sometimes, and horribly failed in some other places. Some urls were correcty constructed, but most were not. I didn't try to figure out which ones were doomed to fail — but it took me an embarassingly long time to realize that prepending a slash solved all problems.
The magical-names of environ
and start_response
are because the flask app is a wsgi_application, and this is the api of wsgi_applications generically.
Now it's ready
Restart the apache server (/apache2/bin/restart
) and go. Note that when incrementally making changes above, some changes can take a few minutes to fully propogate. It's only doing it the first time which takes some thought.
Leave a comment
Info on how to comment
To make a comment, please send an email using the button below. Your email address won't be shared (unless you include it in the body of your comment). If you don't want your real name to be used next to your comment, please specify the name you would like to use. If you want your name to link to a particular url, include that as well.
bold, italics, and plain text are allowed in
comments. A reasonable subset of markdown is supported, including lists,
links, and fenced code blocks. In addition, math can be formatted using
$(inline math)$
or $$(your display equation)$$
.
Please use plaintext email when commenting. See Plaintext Email and Comments on this site for more. Note also that comments are expected to be open, considerate, and respectful.
Comments (1)
2019-05-02 Eli B
Thank god, finally one that works.