Easily Deploy Simple Flask Apps to Heroku

Have a working Flask app you want to deploy to Heroku? Read on to learn how…

Flask and Heroku logos with a gradient between them

A few weeks ago I had a couple of Flask apps that I wanted to deploy to Heroku. My apps ran fine locally so I expected the process to be quite simple with Heroku’s ability to deploy directly from a GitHub repository. While I eventually got both of my apps up and running, I thought the resources I found were a bit too dense for the relatively straightforward process I used to get everything set up. I’m hoping this blog post helps someone in a similar position get their work deployed more quickly than I did. You can follow along as I explain each step or just scroll down for the abridged version at the bottom of this post.

1. Push Your App to a GitHub Repository

This step isn’t completely necessary since you can deploy using a combination of Git and the Heroku CLI, but I would definitely recommend just linking a GitHub repo because it’s much easier. If you aren’t sure how to get your local files into a GitHub repo, check out this guide by Karl Broman. Once you have your app in its own GitHub repository and you’ve confirmed that it runs locally you should be ready for the next step.

2. Set Up a Virtual Environment

Before we start creating the files Heroku needs to properly deploy your app we’re going to set up a virtual environment for your app. You don’t actually have to follow this step but I highly recommend doing it to avoid the messy alternative. That said, if you don’t want to use a virtual environment, feel free to skip ahead to section 3.

So why a virtual environment?

  • A virtual environment allows you to keep specific versions of libraries installed for app development and testing while still allowing you to update everything to the newest version outside of that environment. This is especially handy if you’re using a package management system like Conda that encourages you to update all packages simultaneously.
  • Having a virtual environment for your app makes it easy to get a list of the packages it requires to run, which is exactly what we’ll do in the next step.

Follow this guide from Real Python to set up your virtual environment and make sure it’s running for the remaining steps. If you try to run your app it should fail because your newly-created virtual environment doesn’t have Flask or any of the other Python libraries your app is using installed yet.

ModuleNotFoundError: No module named 'flask'

So in order to make your app functional again, you’ll have to install all necessary packages in the virtual environment using pip. For Flask the shell command looks like this:

(env) $ pip install flask

Once you can launch your app locally from the virtual environment you should be ready for the next step.

3. Install and Test Gunicorn

Regardless of whether or not you created a virtual environment, you’ll need to use Gunicorn to get your app to run properly on Heroku. Flask’s built-in web server cannot handle concurrent requests, but Gunicorn can. For a much better, in-depth explanation check out this Heroku article. What you want to do now is install Gunicorn and make sure you can run your app with it. Let’s first install it:

(env) $ pip install gunicorn

And now navigate to the directory containing your Flask app and let’s make sure it works with Gunicorn. The command below will run the function app from the file app.py contained in the current working directory using Gunicorn. You may need to change the command slightly based on what you named things; just follow this format: gunicorn (your file):(your function).

(env) $ gunicorn app:app

You should see a message telling you that Gunicorn is running and a local address you can open in a web browser. All app functionality should be the same as if you ran it through Flask’s built-in web server.

If your Flask app script is not in the root directory of your GitHub repository, you’ll also want to test a modified version of this command. We’ll be telling Heroku to use Gunicorn to run your app, but it does that from the root directory. So let’s navigate to the repo root directory and test Gunicorn again (if your app script is in the root directory you can just skip this part). The command below uses the –pythonpath flag so Gunicorn can find my app file in a subdirectory. Follow this format for your own file hierarchy: gunicorn –pythonpath (app directory) (your file):(your function).

(env) $ gunicorn --pythonpath flask app:app

If everything works properly, you’re ready to create the three files Heroku needs!

4. Add ‘Procfile’ to Your Repository

The Procfile is what Heroku uses to launch your app and should be easy to create because we just figured out what needs to be in it in the last step. All you have to do now is put web: in front of the Gunicorn command you ran from the root directory of your repository and save that in a plain text file called Procfile in your repo root. My Procfile looks like this:

web: gunicorn --pythonpath flask app:app

That’s it. Just follow the pattern web: gunicorn –pythonpath (app directory) (your file):(your function) and save it in Procfile.

5. Add ‘runtime.txt’ to Your Repository

Next we’ll create runtime.txt, which tells Heroku what version of Python you want to use to run your app. You can get your Python version through the command line by running this line:

(env) $ Python -V

You should see output that looks something like this:

Python 3.7.3

Now all you have to do is properly format the Python version and save it in a plain text file called runtime.txt in the root directory of the repo. For the version above the file contents should look like this:

python-3.7.3

Before deploying you should also check the Heroku Dev Center to confirm that you’re specifying a supported version of Python.

6. Add ‘requirements.txt’ to Your Repository

The last file we need to create is requirements.txt, which tells Heroku exactly what Python libraries your app is dependent upon. Did you set up that virtual environment in Step 2? If you did, congratulations, this step is going to be really simple. Make sure your virtual environment is still activated and run the command below to write a list of the libraries your code needs to run into requirements.txt.

(env) $ pip freeze > requirements.txt

You can also run pip freeze by itself to get the list of libraries without creating the requirements.txt file. If you set up a virtual environment, the list should be pretty short because that environment contains only the libraries your app needs to run. If you didn’t use a virtual environment your list will contain every Python library pip has installed on your machine, which is likely way more than you need just to run your app. You can whittle down that list by manually deleting items from requirements.txt, but that is definitely not a best practice. It’ll work, but I highly recommend using a virtual environment instead.

7. Push Your New Files to GitHub

Once you have Procfile, runtime.txt, and requirements.txt saved locally in your app’s root directory you’ll want to commit and push them to your GitHub repository. Since you’ll be connecting the GitHub repo for the app to Heroku for deployment, all necessary files need to be in your online repository.

8. Connect Your GitHub Repo to a Heroku Project

You’ll need to first create a Heroku account if you don’t already have one and then create a new app through the Heroku web interface. I won’t detail how to do either of those things here because Heroku’s interface makes it very simple. Once you’ve created your app you should see a section called Deployment Method under the Deploy tab: Click Connect to GitHub.

Screenshot of Deployment Method Menu

Once you give Heroku permission to access your GitHub account you’ll be able to search for your app repository and then connect to it.

Screenshot of GitHub connection interface

9. Deploy Your App

With your repository connected you should now be able to deploy it under the Manual Deploy section. Just select what branch you want to use and click Deploy Branch. There’s also an option for automatic deployment which will deploy a new version of your app on Heroku every time you push to the deployed branch on GitHub.

Screenshot of Manual Deploy interface

You can check the results of your build under the Activity tab: if everything worked you should see Build succeeded after your latest build attempt. If you see Build failed check the build log to see what went wrong. I’ve found googling the exact error message from the logs to be very helpful when I’m not sure how to fix a failed build.

Screenshot of Activity Feed

You may also run into a situation where your build succeeds but does not run properly or at all. Because the build was successful, you won’t find any error messages in the build log and will instead have to view the Application Logs. Click the More dropdown menu in the upper right corner of the Heroku dashboard and select View Logs to access them.

Screenshot of Activity Logs button

Googling error messages will again come in handy when troubleshooting activity logs. I was having trouble getting an app that uses OpenCV to run despite a successful build and was able to quickly resolve my problem thanks to a search turning up this StackOverflow thread.

Thanks for Reading!

Hopefully you’ve now successfully deployed your Flask app to Heroku. If it didn’t work for you, please let me know what or where things went wrong so I can make improvements to this post. If you want to dig deeper into Heroku, a good place to start is the well-organized Heroku Dev Center. Thanks for reading and happy Heroku deploying!


TL;DR

Here’s how to deploy a Flask app on Heroku:

  1. Push your working Flask app to a GitHub repository
  2. Set up a virtual environment and install all library dependencies
  3. Install and test Gunicorn locally
    gunicorn --pythonpath (app directory) (file name):(function name)
  4. Add ‘Procfile’
    web: gunicorn --pythonpath (app directory) (file name):(function name)
  5. Add ‘runtime.txt’
    Python -V
  6. Add ‘requirements.txt’
    pip freeze > requirements.txt
  7. Push new files to the GitHub repository
  8. Connect GitHub repository to Heroku and deploy
  9. Deploy your app

ShotPlot Archery App

I’ve long been fascinated by computer vision and had been thinking about using it to develop an automatic archery scoring system for a while. A few years ago, I found out about an archery range (shout out to Gotham Archery!) that had just opened in my neighborhood and decided to check it out. I was hooked after the introductory class and have been shooting there regularly ever since. As I continued to work on improving my form, I found self-evaluation to be somewhat difficult and wanted to come up with a quick and simple way to calculate my scores and shot distributions. While developing my skills at the Metis data science bootcamp, I started to get a clearer vision of how exactly I could build such a tool. My initial app idea involved live object tracking running on a mobile device, which I quickly realized might be too ambitious for a computer vision neophyte. I eventually settled on a plan to analyze a single target photo to derive shot positions and an average shot score for the session.

Data Collection

Before gathering my initial data, I set some restrictions on what each of those images would require. I wanted images to have all four corners of the target sheet visible so I could remove perspective skew and uniformly frame each one. Photos also needed to have enough contrast to pick out the target sheet and shot holes from the background. In order to keep the scope of the project manageable, I only used a single type of target: the traditional single-spot, ten ring variety. With those parameters in mind, I collected target data in two ways over several trips to the aforementioned Gotham Archery; I used my iPhone to photograph my target after each round of shooting at the range and also collected several used targets others had shot from the range’s discard bin. I set up a small home studio to quickly shoot the gathered targets but did not use any special lighting, camera equipment, or a tripod because I wanted the images to represent what an app user could easily produce themselves. I ended up collecting around 40 usable targets (some were too creased or torn) and set aside 11 of those to use as a test set to evaluate the app’s performance.

Images of used targets shot in different locations
Examples of suitable target images

Choosing an Algorithm

With my data in hand I was ready to start writing some code to process images into qualitative values, which meant choosing between one of a couple diverging approaches. Either training a Convolutional Neural Network or a more manual image processing approach would work to calculate scores, but both options come with benefits and important limitations:

Algorithm Pros Cons
CNN Probably less coding Might need more data
High personal interest Only good for score data
Manual Processing Needs less data Probably more coding
Good for scores and positional data Less sexy

Going with a neural network may have been difficult due to the small number of targets I had collected. Even though I could have bolstered the dataset by taking multiple photographs of each target from different angles and orientations I’m still not sure I would have had enough to train a quality model. However the real dealbreaker for me was that a CNN would not be able to provide me with shot coordinates, which I really wanted to help break down an archer’s inconsistencies. Heavily processing images with OpenCV was simply the better solution for my problem, no matter how much I would have liked to work with neural networks on this project.

Image Processing with OpenCV

OpenCV has a vast selection of image processing tools that can be intimidating at first glance and I spent the first few days working with the library just learning what commands might prove useful. Between my own exploration and reading a few blogs, like the incredibly helpful PyImageSearch, I was able to put together a rough plan for deriving shot positions from targets. I needed to do the following:

  • Remove perspective skew to flatten target image
  • Standardize position and orientation of targets
  • Use blob detection to find shot holes

From that outline, I broke down the required work into several smaller steps:

  1. Import image and set color space (OpenCV imports color channels as BGR instead of RGB)
  2. Find target sheet corners
  3. Use corners to remove perspective skew, flattening target sheet
  4. Find scoring region circles on target
  5. Resize image into a square with circles at the center
  6. Partition image by background color
  7. Balance values of each partition to make holes stand out from the background
  8. Recombine image partitions into a single image
  9. Obscure logos at bottom of target sheet to hide them from blob detection
  10. Use blob detection to find holes
  11. Split up large blobs that are actually clusters of shots
  12. Calculate shot scores based on distance from center of target

Sample images from processing steps 1, 3, 5, and 9
Sample target at various stages of processing

You can check out larger versions of the images in the ‘shotplot.ipynb’ notebook in the project GitHub repo, which runs through the entire shot identification process. The actual code for the OpenCV processing lives in the script ‘target_reader.py’ so that it can be easily imported into both a notebook and the Flask app script.

Sample image of target with identified shots circled
Sample target with identified shots circled

Algorithm Performance

In order to evaluate the performance of my image processing code, I manually identified shots on my eleven-target test set and compared the results to what my algorithm found. I then compiled a confusion matrix and recall and precision values for the over 550 shots in the test set:

Metric Score
Test Set Recall .955
Test Set Precision .983
Not Labeled a Shot Labeled a Shot
Not Actually a Shot N/A* 9
Actually a Shot 25 530

* Too many to count

In practice, I was constantly testing the performance of my code against different targets in my ‘training’ set and making adjustments when necessary. I certainly became more dilligent about testing after an early mishap resulted in my algorithm ‘overfitting’ a specific kind of target image and perform significantly worse against others. Another issue I encountered is the subjectivity of shot identification: determining how many shots created some holes is difficult if not impossible. Fortunately, manually identifying most shots is straightforward so I do not think the evaluation statistics would change significantly based on another person’s shot identifications.

Detail images of shots that are difficult to identify
Examples of shots that are difficult to identify

The App

I built the app in Flask and relied heavily upon the D3.js library for visualization. This project was my first foray into D3 and I greatly valued the flexibility and customizability it offers. Other visualization software and libraries like Tableau and Matplotlib had less startup cost but couldn’t faithfully reproduce the clear vision I had in mind for the app. Using D3 also leaves open the possibility of adding interactive features to the charts themselves in future development.

Image of the ShotPlot app
Screenshot of the ShotPlot app

Conclusions

Overall I’m pleased with the results of my first attempt at implementing both computer vision and D3 visualization into a completed project. Although ShotPlot succesfully identifies the vast majority of all shots, it does tend to miss a few that are clearly visible, which I’d like to address in future updates. I also removed some information by obscuring the logos at the bottom of the targets because parts of them were getting misidentified as shots. Ideally I’d like to find a better solution that counts shots in those areas and will be testing some alternatives like using template matching to isolate abnormalities that could be identified as shots. Along with performance improvements, I’m aiming to get the app working properly on smartphones since that is the platform on which it’s most likely to be used. I’d also like to expand the visualizations to really take advantage of D3’s ability to create interactive charts. My long-term goals for ShotPlot include adding analyses across multiple sessions and support for multiple target types and shooting styles.


Check out the full project on my GitHub

Boozehound Cocktail Recommender

This project was a labor of love for me since it combines two of my favorite things: data and cocktails. Ever since my wife signed us up for a drink-mixing class a few years ago I’ve been stirring up various concoctions, both established recipes and original creations. My goal was to create a simple app that would let anyone discover new drink recipes based upon their current favorites or just some descriptive words. Recipe books can be fantastic resources, but I often just want something that tastes like some other drink but different or some combination of flavors and a specific spirit and that’s where a table of contents fails. While I never thought of Boozehound as a replacement for my favorite recipe books I was hoping it could serve as an effective alternative when I just don’t have the patience to thumb through dozens of recipes to find what I want to make.

Photo of a book and index cards containing cocktail recipes

Data Collection

Because I wanted Boozehound to work with descriptions and not just cocktail and spirit names I knew I would be relying upon Natural Language Processing and would need a fair amount of descriptive text with which to work. I also wanted the app to look good so I needed to get my recipes from a resource that also has images for each drink. I started by scraping the well-designed Liquor.com, which has a ton of great recipes and excellent photos. Unfortunately the site has extremely inconsistent write-ups on each cocktail: some are paragraphs long and others only a sentence or two. I wanted more consistent, longer drink descriptions and I found them at The Spruce Eats, which I scraped using BeautifulSoup in the ‘scrape_spruce_eats’ notebook on my project GitHub repo. Spruce Eats doesn’t have the greatest list of recipes, but I was still able to collect roughly 980 separate drink entries from the site, each with an ingredient list, description, and an image URL.

Text Pre-Processing

After getting all of my cocktail recipe data into a Pandas DataFrame, I still needed to format my corpus to prepare it for modeling. I used the SpaCy library to lemmatize words and keep only the nouns and adjectives. SpaCy is both fast and easy to use, which made it ideal for my relatively simple pre-processing. I then used scikit-learn’s TF-IDF implementation to create a matrix of each recipe’s word frequency vector. I chose TF-IDF over other vectorizers because it accounts for word count disparities and some of my drink descriptions are twice as long as others. The app also runs user search strings through the same process and those should certainly be shorter than any cocktail description. My pre-processing and modeling work is stored in the ‘model_spruce_eats’ notebook.

Models

Building my initial model was a fairly simple process of dimension reduction and distance calculations. Since I had a TF-IDF matrix with significantly more columns than rows, I needed some way of condensing those features. I tried a few solutions and Non-Negative Matrix Factorization gave me the most sensible groupings. From there I just calculated pairwise Euclidean distances between the NMF description vectors and a similarly vectorized search string. There was just one problem: my model was such a good recommender that it was way too boring. It relied far too heavily on the frequency of cocktail and spirit names in determining similarity, so a search for margarita would just return ten different variations of margaritas. To make my model more interesting I created a second model that is only able to use words from the description that are not the names of drinks or spirits. Both models are then blended together, which the user can control through the Safe-Weird slider in the Boozehound app.

The image below gives an example of how both models work. Drink and spirit names dominate the descriptions, so the Safe model in the top half has a good chance of connecting words like tequila. The Weird model on the bottom has to make connections using other words, in this case refreshing. Because it has less data with which to work, the Weird model tends to make less relevant recommendations, but they’re often more interesting.

Chart showing an example of Safe and Fun model word similarities

The App

I built the app in Flask and wanted a simple, clean aesthetic reminiscent of a classy cocktail bar. As a result I spent just as much time using CSS to stylize content as I did just getting the app to work properly. Luckily I enjoy design and layout so it was a real pleasure seeing everything slowly come together. My Metis instructors would often bring up the idea of completing the story and to me this project would have been incomplete without a concise and visually-pleasing presentation of recipe recommendations.

Picture of the Boozehound app

Conclusions

While I am pleased with the final product I presented in class at Metis, there’s a lot I still want to address with the Boozehound app. Some of the searches I tested returned odd results that could be improved upon. I’d also like to add some UX improvements like helpful feature descriptions and some initial search suggestions for users who don’t know what they want. Another planned feature is a one-click search button to allow the user to find recipes similar to a drink that shows up as a recommendation without having to type it into the search bar. Boozehound is all about exploration and I want to make rifling through a bunch of new recipes as easy as possible.


Check out the full project on my GitHub