{"id":10437,"date":"2023-10-02T16:15:13","date_gmt":"2023-10-02T15:15:13","guid":{"rendered":"https:\/\/www.blopig.com\/blog\/?p=10437"},"modified":"2023-10-02T16:15:15","modified_gmt":"2023-10-02T15:15:15","slug":"deploying-a-flask-app-part-i-the-gunicorn-wsgi-server","status":"publish","type":"post","link":"https:\/\/www.blopig.com\/blog\/2023\/10\/deploying-a-flask-app-part-i-the-gunicorn-wsgi-server\/","title":{"rendered":"Deploying a Flask app part I: the gunicorn WSGI server"},"content":{"rendered":"\n<p>Last year I wrote a <a href=\"https:\/\/www.blopig.com\/blog\/2022\/10\/using-conda-environments-with-flask-and-apache\/\" data-type=\"link\" data-id=\"https:\/\/www.blopig.com\/blog\/2022\/10\/using-conda-environments-with-flask-and-apache\/\">post<\/a> about deploying Flask apps with Apache\/mod_wsgi when your app&#8217;s dependencies are installed in a conda environment. The year before, in the dark times, I wrote a <a href=\"https:\/\/www.blopig.com\/blog\/2021\/05\/hosting-multiple-flask-apps-using-apache-mod_wsgi\/\" data-type=\"link\" data-id=\"https:\/\/www.blopig.com\/blog\/2021\/05\/hosting-multiple-flask-apps-using-apache-mod_wsgi\/\">post<\/a> about the black magic invocations required to get multiple apps running stably using mod_wsgi. I&#8217;ve since moved away from mod_wsgi entirely and switched to running Flask apps from containers using the gunicorn WSGI server behind an Apache reverse proxy, which has made life immeasurably easier. In this post we&#8217;ll cover running a Flask app on localhost using gunicorn; in Part II we&#8217;ll run our app as a service using Singularity and deploy it to production using Apache as a HTTP proxy server.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">The simplest Flask app<\/h2>\n\n\n\n<p>Suppose we have a minimal Flask app, <code>myapp.py<\/code><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from flask import Flask\n\napp = Flask(__name__)\n\n@app.route('\/')\ndef index():\n    print(\"Hello, Flask!\")<\/pre>\n\n\n\n<p>We can run this using Flask&#8217;s built-in HTTP server as follows:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">flask run myapp:app<\/pre>\n\n\n\n<p>This is perfectly acceptable for local development and testing, but is not suitable for production as Flask&#8217;s built-in server is neither performative nor secure. <\/p>\n\n\n\n<p>Flask is a WSGI (Web Server Gateway Interface) application. WSGI is a standardised interface for web servers to communicate with Python applications, allowing any WSGI application to be run by a WSGI server. A WSGI servers include an HTTP server, allowing them to receive HTTP requests from a client (i.e. your browser) and communicate with a WSGI application (i.e. our Flask app). Many such servers are available, but here we&#8217;ll use the gunicorn WSGI server as it&#8217;s quite efficient and works well out of the box.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Installing gunicorn<\/h2>\n\n\n\n<p>We can install gunicorn using pip:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">pip install gunicorn<\/pre>\n\n\n\n<p>If your app uses a virtualenv or conda environment, you can simply install gunicorn in that environment. If your app will need to pause for extended periods when handling requests (e.g. querying a backend database or waiting for a subprocess) you will want to install a suitable asynchronous worker class to allow your app to continue handling requests while backend code is running. For example, we can install the gevent worker class:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">pip install greenlet\npip install gevent<\/pre>\n\n\n\n<p>We can run our Flask app using gunicorn as follows:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">gunicorn myapp:app<\/pre>\n\n\n\n<p>This will run our app on localhost, just like Flask&#8217;s built-in server. We can specify the port, workers, log location, and many other options. For example, to use four gevent workers for an app running on localhost port 5000:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">gunicorn --bind 127.0.0.1:5000 --worker-class gevent<\/pre>\n\n\n\n<p>There are a lot of options, so you&#8217;ll probably want to use a configuration file. See the <a href=\"https:\/\/docs.gunicorn.org\/en\/latest\/settings.html\" data-type=\"link\" data-id=\"https:\/\/docs.gunicorn.org\/en\/latest\/settings.html\">documentation<\/a> for details. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping up<\/h2>\n\n\n\n<p>And that&#8217;s it! Your Flask app is now running using a production-ready WSGI server. Of course, it&#8217;s running on localhost. To deploy our app, we&#8217;ll want to set up an HTTP proxy server in front to handle things like HTTPS and buffering. In Part II we&#8217;ll set up a basic Apache reverse proxy and make sure our Flask app is ready to run behind a reverse proxy.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last year I wrote a post about deploying Flask apps with Apache\/mod_wsgi when your app&#8217;s dependencies are installed in a conda environment. The year before, in the dark times, I wrote a post about the black magic invocations required to get multiple apps running stably using mod_wsgi. I&#8217;ve since moved away from mod_wsgi entirely and [&hellip;]<\/p>\n","protected":false},"author":47,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","wikipediapreview_detectlinks":true,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"ngg_post_thumbnail":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[14,227,393],"tags":[394,730,152],"ppma_author":[498],"class_list":["post-10437","post","type-post","status-publish","format-standard","hentry","category-howto","category-python-code","category-web-development","tag-flask","tag-gunicorn","tag-python"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"authors":[{"term_id":498,"user_id":47,"is_guest":0,"slug":"fergus","display_name":"Fergus Boyles","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/ba8c419ba77128aad589b66ba7ee13da74f4ce2d3108fd724ddcefa200b51c7b?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/10437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/users\/47"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/comments?post=10437"}],"version-history":[{"count":3,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/10437\/revisions"}],"predecessor-version":[{"id":10445,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/10437\/revisions\/10445"}],"wp:attachment":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/media?parent=10437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/categories?post=10437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/tags?post=10437"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=10437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}