There are many options to deploy Python web applications or WSGI-compliant applications in general. Using Apache or Nginx is a popular choice.
Here I will use the following software, which happens to be 100 % Python:
- Flask, a Python framework for web applications, but other WSGI-compliant applications should work just as well.
- Tornado, a scalable non-blocking web server written in Python
- Supervisor, a system for controlling process state similar to Linux's init. Configuring a standalone daemon is another option, although less flexible.
Step 1
Make sure your Flask application is enclosed in a function.
It's basically something like this:
def create_app(any_parameters_you_like):
app = Flask(__name__)
app.config['SERVER_NAME'] = 'johndoe.com:1234'
return app
Outside of any method, you can write something such as:
app = create_app()
There are several things to note here:
- You need to specify the URL and port that will be used to access the web application.
- By default, the built-in server started on the last line only listens for local connections, "0.0.0.0" will make your application accessible from the outside (LAN and any other network).
Step 2
Create a file my_tornado.py anywhere you like, for instance in the same directory than the app. Here we suppose the create_app() function is in a file named my_module.py.
The content of this file for servers requiring SSL connections is as following
#!/usr/bin/env python
import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(1, '/any/other/directory/to/add/to/your/path')
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import FallbackHandler, RequestHandler, Application
import tornado.web
import my_module
app = my_module.create_app()
application = Application([
(r"/my_app/.*", FallbackHandler, dict(fallback=WSGIContainer(app))),
])
http_server = HTTPServer(application, ssl_options = {
"certfile": "/etc/ssl/certs/my_server.crt",
"keyfile": "/etc/ssl/certs/private/my_server.key",
})
http_server.listen(1234)
print 'Ready.'
IOLoop.instance().start()
As explained, in this example SSL is activated. Also a non standard port (5000) is used. You can change the options and run in insecure mode. For SSL you need to roll your own certificates.
Launch the Python file. The app should be running fine.
Because I was launching this on a Raspberry Pi it took several seconds to start. Thanks to the "Ready" message you will know when the server has completely started.
See if "/yoursite" is accessible.
Because I was launching this on a Raspberry Pi it took several seconds to start. Thanks to the "Ready" message you will know when the server has completely started.
See if "/yoursite" is accessible.
Step 4
Make it start at boot using init.d, xinetd or other techniques. Here I am using Supervisor.
You can optionally enable the web interface in the config /etc/supervisord.conf which will look like this:
Add a new section to the file:
[program:my_app]
command = /home/pi/my_webapp/my_tornado.py
startsecs=10
stopsignal=INT
user=pi
Change the appropriate settings if necessary. Note stopsignal is the signal sent to the process so it stops itself, it's the first argument of "kill". Sending SIGINT has the same effect as pressing Ctrl+C after Tornado has been started.
startsecs is a hint for Tornado : the service is considered running if the process is still up after 10 seconds.
As explained you can also use a standalone daemon, but Supervisor is more flexible and doesn't need any configuration and fiddling with boilerplate code.
Ensure Supervisord starts with the system.
UPDATE: I had a problem recently because one of the services declared with supervisord required another service (started with a regular init.d script) to work. The workaround is to start supervisord after everything elseby appending "$all" at the end of #Required-Start in the init.d script as described at the bottom of this page.
UPDATE: I had a problem recently because one of the services declared with supervisord required another service (started with a regular init.d script) to work. The workaround is to start supervisord after everything elseby appending "$all" at the end of #Required-Start in the init.d script as described at the bottom of this page.
That's it, we're done!