├── .gitignore ├── Procfile ├── README.md ├── app.py ├── readme_assets └── images │ ├── download-heroku-toolbelt.png │ ├── heroku-dashboard-initial-app.png │ ├── heroku-dashboard.png │ ├── heroku-first-app-resources.png │ ├── heroku-first-app.png │ ├── heroku-git-push.gif │ ├── heroku-login.gif │ └── welcome-heroku.png ├── requirements.txt └── runtime.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | readme_assets 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # IPython Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app --log-file=- 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### tl;dr 4 | 5 | A walkthrough on how to get set up with Heroku and its toolkit and then how to deploy a simple web application (for free) on the Heroku Cloud. 6 | 7 | To see a more advanced Flask app that uses USGS data and Google APIs, checkout this repo: [datademofun/heroku-flask-quakes-lesssimple](https://github.com/datademofun/heroku-flask-quakes-lesssimple) 8 | 9 | This is a long README of the steps, but all the actual code and configuration for this app is included in this repo. You can copy and deploy it through your own Heroku account. 10 | 11 | 12 | # Deploying a simple Flask app to the cloud via Heroku 13 | 14 | This walkthrough will acquaint you with the [popular Heroku cloud application platform](https://devcenter.heroku.com/start). Previously, we have been able to create Python Flask apps (see lessons [here](http://www.compjour.org/lessons/flask-single-page/) and [here](http://www.compjour.org/lessons/flask-recalls/)) and run them locally on our own computers. With a cloud service, we are able to put our websites onto the public World Wide Web, with a publicly accessible URL. 15 | 16 | A live version of this lesson's very simple app can be found at this URL: 17 | https://warm-scrubland-16039.herokuapp.com/ 18 | 19 | (it'll be slow because I'm using Heroku's free tier) 20 | 21 | To reduce the number of new moving parts to learn about, [we'll only worry about creating the simplest of Flask apps](http://www.compjour.org/lessons/flask-single-page/) -- the lesser the complexity, the fewer the dependencies, and thus, the fewer the conflicts. So pay attention to the steps that involve touching the Heroku service and toolset. 22 | 23 | Deploying an app on the cloud just means that we're putting code on a computer that we have no direct control over. Writing the Python app code is the same as it was before, but we have to follow a few Heroku conventions before Heroku will execute our code on its own computers. 24 | 25 | Review the lessons on [creating a simple Flask app if you've forgotten how to put together a simple Flask app](http://www.compjour.org/lessons/flask-single-page/). 26 | 27 | Some of the instructions in this tutorial comes from these this official Heroku tutorial: [Getting Started on Heroku with Python](https://devcenter.heroku.com/articles/getting-started-with-python). However, be warned, the official tutorial includes a lot of extra steps that may not apply to your system. I've tried to filter them to a minimum. 28 | 29 | 30 | ------------------ 31 | 32 | 33 | 34 | # Sign up for Heroku 35 | 36 | Heroku, being a Software as a Service (SaaS)-type of service, requires you to create an account and login before you can start using its computers. Don't worry, creating an account and running a simple app is free and doesn't require a credit card. 37 | 38 | You can create an account at this URL: [https://signup.heroku.com/dc](https://signup.heroku.com/dc) 39 | 40 | ## Download the Heroku toolbelt 41 | 42 | Heroku has a command-line "toolbelt" that we must download and install in order commands that will simplify our communication with the Heroku servers. The toolbelt can be downloaded at: [https://toolbelt.heroku.com/](https://toolbelt.heroku.com/) 43 | 44 | ![image download-heroku-toolbelt.png](readme_assets/images/download-heroku-toolbelt.png) 45 | 46 | ## Authenticate with Heroku with `heroku login` 47 | 48 | Installing the Heroku toolbelt will give you access to the `heroku` command which has several subcommands for interacting with the Heroku service. 49 | 50 | The first command you need to run is `heroku login`, which will ask you to enter your login credentials so that every subsequent `heroku` command knows who you are: 51 | 52 | (You will only have to do this __once__) 53 | 54 | ~~~sh 55 | $ heroku login 56 | ~~~ 57 | 58 | ![readme_assets/images/heroku-login.gif](readme_assets/images/heroku-login.gif) 59 | 60 | 61 | 62 | # Let's create a Flask app 63 | 64 | Create a basic Flask app of any kind, i.e. one that consists of just __app.py__. You can revisit the [lessons here](http://www.compjour.org/lessons/flask-single-page/hello-tiny-flask-app/) or the sample repo at: [https://github.com/datademofun/heroku-basic-flask](https://github.com/datademofun/heroku-basic-flask) 65 | 66 | Heck, try to write it out by memory if you can -- below, I've made the app output simple HTML that includes the current time and a placeholder image from the [loremflickr.com](http://loremflickr.com/) service: 67 | 68 | ~~~py 69 | from flask import Flask 70 | from datetime import datetime 71 | app = Flask(__name__) 72 | 73 | @app.route('/') 74 | def homepage(): 75 | the_time = datetime.now().strftime("%A, %d %b %Y %l:%M %p") 76 | 77 | return """ 78 |

Hello heroku

79 |

It is currently {time}.

80 | 81 | 82 | """.format(time=the_time) 83 | 84 | if __name__ == '__main__': 85 | app.run(debug=True, use_reloader=True) 86 | ~~~ 87 | 88 | You should be able to run this app on your own system via the familiar invocation and visiting [http://localhost:5000](http://localhost:5000): 89 | 90 | ~~~sh 91 | $ python app.py 92 | ~~~ 93 | 94 | 95 | # Specifying dependencies for deploying Heroku 96 | 97 | (the following comes from Heroku's guide to [Deploying Python and Django Apps on Heroku](https://devcenter.heroku.com/articles/deploying-python)) 98 | 99 | Our simple Flask app has has a couple of __dependencies__: the Python interpreter and the Flask library (duh). Which means that Python and the Flask library must be installed on our computer. 100 | 101 | When we talk about deploying our app onto Heroku, or any cloud service, we are working with _someone else's computer_. And, for the most part, we can't count on "someone else's computer" to have the same software stack as we do. 102 | 103 | With Heroku, we have to include some metadata with our application code, so that Heroku knows how to set up a compatible webserver and install the software that our application needs. The metadata can be as simple as including a few plaintext files, which I list below in the next section. 104 | 105 | ## Installing the gunicorn web server 106 | 107 | Whenever we run `python app.py` from our command-line, we're running the default webserver that comes with Flask. However, Heroku seems to prefer a [web server called __gunicorn__](https://devcenter.heroku.com/articles/python-gunicorn). Just so that we can follow along with Heroku's documentation, let's install gunicorn on our own system. It's a Python library like any other and can be installed with __pip__: 108 | 109 | ~~~sh 110 | $ pip install gunicorn 111 | ~~~ 112 | 113 | ## Adding a requirements.txt 114 | 115 | By convention, Python packages often include a plaintext file named __requirements.txt__, in which the dependencies for the package are listed on each line. 116 | 117 | Create an empty file named `requirements.txt` (in the same root folder as __app.py__). 118 | 119 | So, what our are dependencies? For starters, __Flask__. So, add it to __requirements.txt__ (it's case sensitive): 120 | 121 | ~~~ 122 | Flask 123 | ~~~ 124 | 125 | Even though it's not part of our __app.py__, we did just install __gunicorn__ -- because I said to -- so let's throw that into __requirements.txt__: 126 | 127 | ~~~ 128 | Flask 129 | gunicorn 130 | ~~~ 131 | 132 | 133 | ## Specifying Python version with `runtime.txt` 134 | 135 | Heroku will know that we be running a Python app, but because there's a huge disparity between Python versions (notably, [Python 2 versus 3](https://wiki.python.org/moin/Python2orPython3)), we need to tell Heroku to use the Python version that we're using on our own computer to develop our app. 136 | 137 | Which version of Python are we/you running? From your command line, run the Python interpreter with the `--version` flag: 138 | 139 | ~~~sh 140 | $ python --version 141 | Python 3.5.1 :: Anaconda 2.5.0 (x86_64) 142 | ~~~ 143 | 144 | Nevermind that `"Anaconda"` bit -- we just need the version number, e.g. __3.5.1__ 145 | 146 | 147 | Create __runtime.txt__ in your root app folder and add just the single line (note: replace my example version number with yours, if it is different): 148 | 149 | ~~~sh 150 | python-3.5.1 151 | ~~~ 152 | 153 | ## Create a `Procfile` 154 | 155 | OK, one more necessary plaintext file: [Heroku needs a file to tell it how to start up the web app](https://devcenter.heroku.com/articles/deploying-python#the-procfile). By convention, this file is just a plaintext file is called (and named): __Procfile__: 156 | 157 | > A Procfile is a text file in the root directory of your application that defines process types and explicitly declares what command should be executed to start your app. 158 | 159 | 160 | And for now, __Procfile__ can contain just this line: 161 | 162 | ~~~ 163 | web: gunicorn app:app --log-file=- 164 | ~~~ 165 | 166 | 167 | ## Add a .gitignore file 168 | 169 | This is not necessary for running an app, but we want to prevent unwanted files from being pushed up to Heroku Git repo (or any git repo) later on. Create a new file named `.gitignore` and copy the contents of the example `.gitignore` file here: 170 | 171 | [https://github.com/github/gitignore/blob/master/Python.gitignore](https://github.com/github/gitignore/blob/master/Python.gitignore) 172 | 173 | 174 | 175 | ## Run `heroku local` 176 | 177 | So now our simple Flask app folder contains this file structure: 178 | 179 | ~~~ 180 | ├── .gitignore 181 | ├── Procfile 182 | ├── app.py 183 | ├── requirements.txt 184 | └── runtime.txt 185 | ~~~ 186 | 187 | Before we deploy our app on to Heroku, we'll want to test it on our own system _again_ -- but this time, instead of using `python app.py`, [we'll use the Heroku toolbelt subcommand, __local__](https://devcenter.heroku.com/articles/deploying-python#build-your-app-and-run-it-locally): 188 | 189 | ~~~sh 190 | $ heroku local web 191 | ~~~ 192 | 193 | It will create an app at [http://localhost:5000](http://localhost:5000), which should work like before when you ran `python app.py.` 194 | 195 | What was the point of that? Go into your `Procfile`, delete the single line that we put into it, save it, then try `heroku local web` again. You'll get an error message because Heroku won't know how to build the application: 196 | 197 | ~~~sh 198 | $ heroku local web 199 | [WARN] No ENV file found 200 | [WARN] Required Key 'web' Does Not Exist in Procfile Definition 201 | ~~~ 202 | 203 | 204 | # Setting up our app's Git repo 205 | 206 | This part will be a little confusing. Heroku deploys using __git__ -- which is _not to be confused with_ __Github__. (Hopefully, you have __git__ installed at this point.) 207 | 208 | Basically, this means before we can deploy to Heroku, we need to create a git repo in our app, add the files, and commit them. But we _don't_ need to push them onto a Github repo if we don't want to. 209 | 210 | In fact, for this basic app, don't bother making a Github repo. Just make a local git repo: 211 | 212 | ~~~sh 213 | $ git init 214 | $ git add . 215 | $ git commit -m 'first' 216 | ~~~ 217 | 218 | 219 | # Creating a Heroku application 220 | 221 | OK, now Heroku has all it needs to provision a server for our application. 222 | 223 | Now we need to do two steps: 224 | 225 | 1. Tell Heroku to initialize an application [via its __create__ command](https://devcenter.heroku.com/articles/creating-apps). 226 | 2. Tell Heroku to deploy our application by pushing our code onto the Git repo hosted on Heroku. 227 | 228 | 229 | ## Initializing a Heroku application 230 | 231 | First, make sure you've successfully created a Git repo in your app folder. Running `git status` should, at the very least, not give you an error message telling you that you've yet to create a Git repo. 232 | 233 | Then, run this command: 234 | 235 | ~~~sh 236 | $ heroku create 237 | ~~~ 238 | 239 | The __create__ subcommand sets up a URL that your application will live at, and a Git repo from which you'll be pushing your code to on deployment. 240 | 241 | The `heroku create` command results in output that looks like this: 242 | 243 | ~~~stdout 244 | Creating app... ⬢ warm-scrubland-16039 245 | https://warm-scrubland-16039.herokuapp.com/ | https://git.heroku.com/warm-scrubland-16039.git 246 | ~~~ 247 | 248 | That output tells us two things: 249 | 250 | 1. Our application can be visited at: `https://boiling-journey-47934.herokuapp.com/` 251 | 2. Heroku has git repo at the url `https://git.heroku.com/boiling-journey-47934.git`...In fact, the `create` command has helpfully set up a _remote_ named _heroku_ for us to __push__ to. 252 | 253 | If you visit your application's app, e.g. `https://some-funnyword-9999.herokuapp.com/`. you'll get a placeholder page: 254 | 255 | ![image welcome-heroku.png](readme_assets/images/welcome-heroku.png) 256 | 257 | That's not what we programmed our app to do -- so that's just a page that comes from Heroku -- we haven't really deployed our app yet. But Heroku is ready for us. You can further confirm this by visiting [https://dashboard.heroku.com/](https://dashboard.heroku.com/) and seeing your application's URL at the bottom: 258 | 259 | ![image heroku-dashboard.png](/readme_assets/images/heroku-dashboard.png) 260 | 261 | Clicking on that application entry will reveal a page that is empty of "processes": 262 | 263 | ![image heroku-dashboard-initial-app.png](readme_assets/images/heroku-dashboard-initial-app.png) 264 | 265 | 266 | As for that Git repo that Heroku created for us...run this command to see the endpoints: 267 | 268 | ~~~sh 269 | $ git remote show heroku 270 | ~~~ 271 | 272 | The output: 273 | 274 | ~~~sh 275 | * remote heroku 276 | Fetch URL: https://git.heroku.com/warm-scrubland-16039.git 277 | Push URL: https://git.heroku.com/warm-scrubland-16039.git 278 | HEAD branch: (unknown) 279 | ~~~ 280 | 281 | 282 | 283 | ## Deploying our application code 284 | 285 | OK, let's finally __deploy our app__. We tell Heroku that we want to deploy our currently committed code by doing a `git push` to `heroku master`: 286 | 287 | ~~~sh 288 | $ git push heroku master 289 | ~~~ 290 | 291 | This should seem familiar to when you've pushed code to your __Github account__, but targeting `origin master`: 292 | 293 | ~~~sh 294 | $ git push origin master 295 | ~~~ 296 | 297 | ...but of course, we haven't actually created a __Github__ git repo for our simple app...we've only created a __local repo__. And, by running `heroku create`, we also created a repo on Heroku...which we will now push to: 298 | 299 | ~~~sh 300 | $ git push heroku master 301 | ~~~ 302 | 303 | And with that simple command, Heroku will go through the steps of taking our application code, installing the dependencies we specified in `requirements.txt` and `runtime.txt`, and then starting a webserver as specified in `Procfile`: 304 | 305 | (this process takes a lot longer than simply pushing code onto Github to save) 306 | 307 | After about 30 seconds, you'll get output telling you how to find your application on the web: 308 | 309 | 310 | ![readme_assets/images/heroku-git-push.gif](readme_assets/images/heroku-git-push.gif) 311 | 312 | ~~~sh 313 | remote: https://warm-scrubland-16039.herokuapp.com/ deployed to Heroku 314 | remote: 315 | remote: Verifying deploy.... done. 316 | To https://git.heroku.com/warm-scrubland-16039.git 317 | 1c6e386..b0e9510 master -> master 318 | ~~~ 319 | 320 | My app happens to be given the name __warm-scrubland-16039__, which means that it is now available at the following URL for the whole world: 321 | 322 | [https://warm-scrubland-16039.herokuapp.com/](https://warm-scrubland-16039.herokuapp.com/ ) 323 | 324 | And that's how you make your application available to the world. 325 | 326 | ## Scaling the app with dynos 327 | 328 | Heroku [has this concept of __dynos__](https://devcenter.heroku.com/articles/dynos), an abstraction of the servers used to host your app and do its computational work. The free account lets you run apps on a single dyno...and by default, your new apps should have a single dyno upon creation. But just incase it doesn't, run this heroku command: 329 | 330 | 331 | ~~~sh 332 | $ heroku ps:scale web=1 333 | ~~~ 334 | 335 | 336 | # Saving your application to Github 337 | 338 | For homework purposes -- though not necessarily _this_ app -- you'll want to push your application code to your Github account as well. 339 | 340 | You need to create an entirely new repo on Github, e.g. 341 | 342 | https://github.com/your_username/fun_flask_app 343 | 344 | 345 | Which will result in these instructions: 346 | 347 | > ### …or push an existing repository from the command line 348 | > 349 | > `git remote add origin git@github.com:your_username/fun_flask_app.git` 350 | > 351 | 352 | 353 | Replace `your_username` and `fun_flask_app` with the appropriate names. Then add run the given command: 354 | 355 | ~~~sh 356 | git remote add origin git@github.com:your_username/fun_flask_app.git 357 | ~~~ 358 | 359 | The __git add__ and __git commit__ commands stay the same no matter how many repos you push to. But you have to push to each repo specifically and separately: 360 | 361 | To get your code on Github: 362 | 363 | ~~~ 364 | git push origin master 365 | ~~~ 366 | 367 | And, again, to get it deployed on Heroku: 368 | 369 | ~~~ 370 | git push heroku master 371 | ~~~ 372 | 373 | 374 | # Changing our application code 375 | 376 | Altering the codebase of a Heroku-deployed app is not much different than how we've re-edited and saved code before, except that we have to run __git push heroku master__ in order to update the application on the Heroku server -- Heroku's server doesn't have a mind-meld with our computer's hard drive, we have to notify it of our changes via a `git push`. 377 | 378 | However, `git push` doesn't push anything until we've actually changed code -- and added and committed those changes via `git add` and `git commit`. 379 | 380 | Give it a try. Change __app.py__. Then add/commit/push: 381 | 382 | ~~~sh 383 | git add --all 384 | git commit -m 'changes' 385 | git push heroku master 386 | ~~~ 387 | 388 | Depending on how much you've altered the code base, the push/deploy process may take just as long as the initial install. But that's a reasonable price to pay for an easy process for updating an application that the entire world can access. 389 | 390 | 391 | # Managing your Heroku apps 392 | 393 | If you plan on using Heroku to deploy your apps but _not while not paying a monthly bill_, you'll only be able to deploy one live app at a time. 394 | 395 | To __destroy__ an app, which will destroy the deployed version and the reserved URL -- but _not_ your local code -- you can select your app via the [Heroku web dashboard](https://dashboard.heroku.com/apps), then delete it via its configuration/settings menu. 396 | 397 | Or, if you'd rather do it from the command-line with the Heroku toolbelt, use the __apps:destroy__ subcommand: 398 | 399 | ~~~sh 400 | $ heroku apps:destroy whatever-yourappnameis-99999 401 | ~~~ 402 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from datetime import datetime 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def homepage(): 7 | the_time = datetime.now().strftime("%A, %d %b %Y %l:%M %p") 8 | 9 | return """ 10 |

Hello heroku

11 |

It is currently {time}.

12 | 13 | 14 | """.format(time=the_time) 15 | 16 | if __name__ == '__main__': 17 | app.run(debug=True, use_reloader=True) 18 | 19 | -------------------------------------------------------------------------------- /readme_assets/images/download-heroku-toolbelt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/download-heroku-toolbelt.png -------------------------------------------------------------------------------- /readme_assets/images/heroku-dashboard-initial-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/heroku-dashboard-initial-app.png -------------------------------------------------------------------------------- /readme_assets/images/heroku-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/heroku-dashboard.png -------------------------------------------------------------------------------- /readme_assets/images/heroku-first-app-resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/heroku-first-app-resources.png -------------------------------------------------------------------------------- /readme_assets/images/heroku-first-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/heroku-first-app.png -------------------------------------------------------------------------------- /readme_assets/images/heroku-git-push.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/heroku-git-push.gif -------------------------------------------------------------------------------- /readme_assets/images/heroku-login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/heroku-login.gif -------------------------------------------------------------------------------- /readme_assets/images/welcome-heroku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datademofun/heroku-basic-flask/0745eecce4dd198792d66e56e244e945c2fa9eb5/readme_assets/images/welcome-heroku.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | gunicorn 3 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.5.1 2 | --------------------------------------------------------------------------------