{"id":1289,"date":"2015-07-04T00:03:10","date_gmt":"2015-07-03T12:03:10","guid":{"rendered":"http:\/\/p-s.co.nz\/wordpress\/?p=1289"},"modified":"2015-07-25T15:51:04","modified_gmt":"2015-07-25T03:51:04","slug":"simple-flask-app-on-heroku-all-steps-almost","status":"publish","type":"post","link":"http:\/\/p-s.co.nz\/wordpress\/simple-flask-app-on-heroku-all-steps-almost\/","title":{"rendered":"Simple flask app on heroku &#8211; all steps (almost)"},"content":{"rendered":"<p>Note &#8211; instructions assume Ubuntu Linux.<\/p>\n<p>See <a href=\"https:\/\/devcenter.heroku.com\/articles\/getting-started-with-python-o\" target=\"_blank\">Getting Started with Python on Heroku (Flask)<\/a> for the official instructions. The instructions below tackle things differently and include redis-specific steps. <\/p>\n<p>Don&#8217;t need postgresql for my app even though needed for heroku demo app. Using redis for simple key-value store.<\/p>\n<p>Main reason for each step is indicated in bold at start. There are lots of steps but there are lots of things being achieved. And each purpose only requires a few steps so probably hard to streamline any further. <\/p>\n<ol>\n<li>APP &#038; BEST PRACTICE<br \/>\n&GT;&GT; sudo apt-get install python3 python3-pip python-virtualenv git ruby redis-server redis-tools<\/li>\n<li>HEROKU<br \/>\nGet free heroku account<\/li>\n<li>HEROKU<br \/>\nInstall heroku toolbelt <a href=\"https:\/\/devcenter.heroku.com\/articles\/getting-started-with-python#set-up\" target=\"_blank\">Heroku setup<\/a>. Sets up virtualenvwrapper for you too (one less thing to figure out)<\/li>\n<li>HEROKU<br \/>\nDo the once-ever authentication<br \/>\n&GT;&GT; heroku login<\/li>\n<li>APP<br \/>\nMake project folder e.g.<br \/>\n&GT;&GT; mkdir ~\/projects\/myproj<\/li>\n<li>APP<br \/>\n&GT;&GT; cd ~\/projects\/myproj<\/li>\n<li>HEROKU<br \/>\n&GT;&GT; echo &#8220;web: python main.py&#8221; &GT; Procfile<\/li>\n<li>HEROKU &#038; BEST PRACTICE<br \/>\n&GT;&GT; git init<\/li>\n<li>HEROKU &#038; BEST PRACTICE<br \/>\n&GT;&GT; mkvirtualenv sticky<\/p>\n<p>So requirements for specific project can be separated from other project &#8211; lets heroku identify actual requirements. Normally &#8220;workon sticky&#8221; thereafter; deactivate to exit virtual env<\/li>\n<li>APP<br \/>\n&GT;&GT; pip3 install flask<br \/>\nNote &#8211; installed within virtualenv<\/li>\n<li>HEROKU<br \/>\nSave the following as requirements.txt &#8211; needed by heroku so it knows the dependencies. Update version of redis as appropriate. gunicorn is a better approach than the flask test server<br \/>\n<code>flask<br \/>\ngunicorn<br \/>\nredis==2.10.3<br \/>\n<\/code>\n<\/li>\n<li>HEROKU<br \/>\nSo we can use Python 3.4 instead of the current default of 2.7:<br \/>\n&GT;&GT; echo &#8220;python-3.4.3&#8221; > runtime.txt\n<\/li>\n<li>APP &#038; HEROKU\n<p>Make a toy app to get started from.<\/p>\n<p>Note &#8211; modify the standard demo flask app to add a port to ease eventual heroku deployment. Otherwise the app will fail because of a problem with the port when running <\/p>\n<p><code>heroku ps:scale web=1<\/code><\/p>\n<p><code>Starting process with command `python main.py`<br \/>\n...<br \/>\nWeb process failed to bind to $PORT within 60 seconds of launch<\/code><\/p>\n<p>Here is an example (may need updating if flask changes):<\/p>\n<p><code>import os<br \/>\nfrom flask import Flask<br \/>\napp = Flask(__name__)<\/p>\n<p>@app.route(\"\/\")<br \/>\ndef hello():<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;return \"Hello World!\"<\/p>\n<p>if __name__ == \"__main__\":<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;port = int(os.environ.get(\"PORT\", 33507))<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;app.run(host='0.0.0.0', port=port)<\/code>\n<\/li>\n<li>BEST PRACTICE<br \/>\n&GT;&GT; deactivate<\/li>\n<li>\nMake a module to make it easier to work with redis &#8211; let&#8217;s call it store.py:<br \/>\n<code><br \/>\nimport os<br \/>\nimport urllib<br \/>\nimport redis<\/p>\n<p>url = urllib.parse.urlparse(os.environ.get('REDISTOGO_URL',<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;'redis:\/\/localhost:6379'))<br \/>\nredis = redis.Redis(host=url.hostname, port=url.port, db=0,<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;password=url.password)<br \/>\n<\/code><br \/>\nWe can then use redis like this:<br \/>\n<code>from store import redis<\/code>\n<\/li>\n<li>APP<br \/>\nKeep building app locally. The following is good for redis: <a href=\"http:\/\/redis-py.readthedocs.org\/en\/latest\/\" target=\"_blank\">Redis docs<\/a>. And flasks docs are always good: <a href=\"http:\/\/flask.pocoo.org\/docs\/0.10\/quickstart\/#a-minimal-application\" target=\"_blank\">Flask Docs &#8211; Minimal Application<\/a><\/li>\n<li>HEROKU  &#038; BEST PRACTICE\n<p>Before deploying to production:<\/p>\n<ol>\n<li>Update git otherwise you&#8217;ll be deploying old code &#8211; heroku uses git for deployment<\/li>\n<li>set app.debug to False (although no rush when just getting started and not expecting the app to get hit much)<\/li>\n<li>probably switch to gunicorn sooner or later (will need to change ProcFile to<br \/>\n<code>web: gunicorn main:app --workers $WEB_CONCURRENCY<\/code><br \/>\n)<\/li>\n<li>\nExample nginx.conf:<br \/>\n<code><br \/>\n# As long as \/etc\/nginx\/sites-enable\/ points to<br \/>\n# this conf file nginx can use it to work with<br \/>\n# the server_name defined (the name of the file<br \/>\n# doesn't matter - only the server_name setting)<br \/>\n# sudo ln -s \/home\/vagrant\/src\/nginx.conf ...<br \/>\n# &nbsp;&nbsp;&nbsp;&nbsp;... \/etc\/nginx\/sites-enabled\/myproj.com<br \/>\n# Confirm this link is correct<br \/>\n# e.g. less \/etc\/nginx\/sites-enabled\/myproj.com<\/p>\n<p>server {<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;listen 80;<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;server_name localhost;<\/p>\n<p>&nbsp;&nbsp;&nbsp;&nbsp;location \/static { # static content is<br \/>\n<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# handled directly by NGINX which means<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# the nginx user (www-data) will need<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# read permissions to this folder <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;root \/home\/vagrant\/src;<br \/>\n<br \/>&nbsp;&nbsp;&nbsp;&nbsp;}<\/p>\n<p>&nbsp;&nbsp;&nbsp;&nbsp;location \/ { # all else passed to Gunicorn to handle<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Pass to wherever I bind Gunicorn to serve to<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Only gunicorn needs rights to read, write,<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# and execute scripts in the app folders<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proxy_pass http:\/\/127.0.0.1:8888;<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;}<br \/>\n}<br \/>\n<\/code>\n<\/li>\n<li>\nExample gunicorn.conf<br \/>\n<code>import multiprocessing<\/p>\n<p>bind = \"127.0.0.1:8888\" # ensure nginx passes to this port<br \/>\nlogfile = \"\/home\/vagrant\/gunicorn.log\"<br \/>\nworkers = multiprocessing.cpu_count() * 2 + 1<br \/>\n<\/code>\n<\/li>\n<\/ol>\n<\/li>\n<li>HEROKU<br \/>\n&GT;&GT; heroku create<\/p>\n<p>Should now be able to browse to the url supplied as stdout fom command e.g.<br \/>\nhttps:\/\/not-real-1234.herokuapp.com\/. Note &#8211; not working yet &#8211; still need to deploy to new app<\/p>\n<p>&GT;&GT; git push heroku master<\/p>\n<p>Must then actually spin up the app:<\/p>\n<p>&GT;&GT; heroku ps:scale web=1<\/p>\n<p>A shortcut for opening is<\/p>\n<p>&GT;&GT; heroku open\n<\/li>\n<li>HEROKU<br \/>\nAdd redis support (after first deployment &#8211; otherwise<br \/>\n<code><br \/>\n !    No app specified.<br \/>\n !    Run this command from an app folder or specify which app to use with --app APP.<br \/>\n<\/code>)<br \/>\n&GT;&GT; heroku addons:create redistogo<\/p>\n<p>Note &#8211; need to register credit card to use any add-ons, even if free ones. Go to <a href=\"https:\/\/heroku.com\/verify\" target=\"_blank\">https:\/\/heroku.com\/verify<\/a>\n<\/li>\n<\/ol>\n<p>Some other points: when developing on a different machine, I needed to supply my public key to heroku from that other machine (<a href=\"http:\/\/stackoverflow.com\/questions\/4269922\/permission-denied-publickey-when-deploying-heroku-code-fatal-the-remote-end\" target=\"_blank\">Permission denied (publickey) when deploying heroku code. fatal: The remote end hung up unexpectedly<\/a>).<\/p>\n<p><code>heroku keys:add ~\/.ssh\/id_rsa.pub<\/code><\/p>\n<p>And the full sequence for upgrading your app after the prerequisites have been fulfilled is:<\/p>\n<ol>\n<li>git commit to local repo<\/li>\n<li>Then git push to heroku<\/li>\n<li>Then run <code>heroku ps:scale web=1<\/code> again<\/li>\n<\/ol>\n<p>And I had a problem when I switched from Python 2 to 3 with redis &#8211; my heroku push wouldn&#8217;t work. By looking at the logs (&GT;&GT; heroku logs &#8211;tail) I found that import imap wouldn&#8217;t work and searching on that generally found I needed a newer version of redis than I had specified foolishly in requirements.txt.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Note &#8211; instructions assume Ubuntu Linux. See Getting Started with Python on Heroku (Flask) for the official instructions. The instructions below tackle things differently and include redis-specific steps. Don&#8217;t need postgresql for my app even though needed for heroku demo &hellip; <a href=\"http:\/\/p-s.co.nz\/wordpress\/simple-flask-app-on-heroku-all-steps-almost\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,11,3,2],"tags":[],"class_list":["post-1289","post","type-post","status-publish","format-standard","hentry","category-open-source","category-programming","category-python","category-web-development"],"_links":{"self":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts\/1289"}],"collection":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/comments?post=1289"}],"version-history":[{"count":57,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts\/1289\/revisions"}],"predecessor-version":[{"id":1348,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/posts\/1289\/revisions\/1348"}],"wp:attachment":[{"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/media?parent=1289"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/categories?post=1289"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/p-s.co.nz\/wordpress\/wp-json\/wp\/v2\/tags?post=1289"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}