Deploying Python Web Apps to Heroku

************* Work in Progress Summer 2021 **************

Pushing your app to Heroku as a ‘production’ environment is a good way to show your work to other people, and for you to ensure that others can install it as you envisage. Whether you’re deploying a Flask or Django application some steps are the same. You can find more details on Heroku on flask and Django deployments.

Heroku is an easy to use deployment environment that runs on top of Amazon Web Services.

You should deploy your app as soon as possible, even if it’s only ‘hello world’ at the start. By doing this early, there are fewer moving parts for you to worry about, and you can resolve any issues now, in case it takes you longer than expected.

We’ll go through a simple application with an SQLite database as this is the easier version. Later, we’ll discuss how to deploy to a different database. SQLite is file based, so slow, and has other limitations, so is not ideal for production, but is fine for prototyping ideas as you start your application. You can move to Postgresql later.

Deploy to Heroku with Git

You need to have your application in a git repository in order to deploy it to Heroku. If you haven’t already done, this, then make your application a git repository.

Install Heroku to Your System

The easiest way to install Heroku (after you’ve created an account there) is with via a terminal with this command:

      curl https://cli-assets.heroku.com/install.sh | sh

This will download the CLI (command line interface)  as a standalone installation for you to use.

Now you can use the command ‘heroku login -i’ to first login to Heroku from the command line (this will pop open a browser window for you to login, which you can then close again).

Now use ‘Heroku create’ from the command line inside your application to add Heroku for deployment. This will generate an ‘app’ on the Heroku platform, and then add it as a remote repository to your regular git settings for this application.

With this in place, now you should also add your public ssh key to Heroku, so that you don’t have to enter your username and password every time you want to push your code there.

Prepare Your App for Deployment

With Heroku added you now need to provide some details about your application before you can deploy it.

Django Specific Steps

Django needs to have you add some settings to the application before you move it to production.

Add the whitenoise library to handle static files such as CSS and images for your application. Install this with

    pip install whitenoise

Then open the settings.py file and add this line after the SecurityMiddleware one:

    'whitenoise.middleware.WhiteNoiseMiddleware',

In setttings.py also add the URL of your application on Heroku, the one with app-name.herokuapp.com to the ALLOWED_HOST list. Don’t include the trailing ‘/’ in the URL.

If you’re using Postgresql, then you’ll need to look further below for database details.

General Steps for Deployment

While you don’t need to run it locally, adding Gunicorn to your application now means that you ‘could’ run it locally, and you automatically have the correct version for running on Heroku too. You can’t use the server for either Flask or Django in production as these are too slow, and are less stable as well. Heroku has more options available, so these are the basics. So use Pip to install Gunicorn with the command:

    pip install gunicorn

Now we can add the other files that Heroku will also need to run your application.

Create a file called ‘runtime.txt and put your version of python in it, for example ‘python-3.7.0’ without the quotes. You should check that it matches the python versions used by Heroku.

We need a file that lists the libraries used in the application. We can generate that with this command:

    python3 -m pip freeze > requirements.txt

This will use pip to generate a list of libraries and write them to the ‘requirements.txt’ file, which is needed by Heroku to install these libraries for our application.

Create a file by the name ‘Procfile’ with no extension and put a line like this in it.

For Flask use this:

    web: gunicorn <app name>:app

Substitute <app name> for the name of your application.

For Django use this:

    web: gunicorn mysite.wsgi --log-file -

In this case, ‘mysite’ is the name of the application, so you may need to change this.

We can now add these new files to our Git repo, and then commit the changes to the repository. Use this command to do that:

    git push heroku main (or master)

You will need to use either main or master depending upon the settings of your repository. After you run this you’ll see a long list of code passing before you as the app is uploaded, unpacked, and then prepared for launching.

Expected Errors and How to Debug Them

You need to pull down the log files for your application in order to see what has gone wrong, as the applications will not be run in ‘debug’ mode as this is insecure. You can retrieve the logs with the command:

    heroku logs

With this running (or using the tail command too, ‘heroku logs -tail’ so that you see a stream of log files) you can try different things to see what is causing the problem. If your app is not deploying correctly, then read the text to see what might be causing the problem.

If you see an error, and fix the code, then you MUST create a new commit in your repo, which you can push to Heroku. If you run ‘push’ without creating a new repo, then you are pushing the old code.

Databases on Heroku

If you’re using SQLite, then all of the above will get your application up and started if you’re using Flask. If you’re using Django, then you need to follow a few more steps. If your database is SQLite, then it might have been uploaded to Heroku with the tables and data included. If not, then you need to run the migrations to create the tables, then you can do whatever is needed to add the data to the tables.

If you need to deploy your app to Postgresql, then you need to include this as an ‘Add-on’ in your Heroku settings so that it provisions a database for you. You can check database content and details by installing PgAdmin to manage your local instance, as well as your remote one. You will need to find the connection details to your Heroku Postgresql instance. Remember too, that if you’re using Postgresql, that your column name can’t start with a capital letter. If you do that, then they are seen as constants, and the columns are not found in queries.

Flask with Postgresql

This option will need to have you doing most of the work yourself in order to provision the database, and keep it up to date after you’ve created an instance on Heroku for you to use. You can do this with PgAdmin as noted already. PgAdmin will allow you to dump your schema and data locally, and then connect to the remote version and upload the schema and data to the remote system.

Django with Postgresql

As Django allows you to use manage.py to handle migrations and other commands, you can do much of the work via the command line. For example, instead, of ‘python3 manage.py migrate’, you can now use ‘heroku run python manage.py migrate’. This convention of adding ‘heroku run’ to the usual manage.py commands, makes life easier.

We need to add two other libraries for the database, dj-database-url for distinguishing local from remote databases, and the psycopg2-binary as the library for connecting to Postgresql. You can install both via pip with these commands:

    pip install dj-database-url

    pip install psycopg2-binary

The dj-database-url needs some configuration in the settings.py file as well. Add this to the file, along with an import statement at the top of the file.

# Heroku: Update database configuration from $DATABASE_URL.
import dj_database_url
db_from_env = dj_database_url.config(conn_max_age=500)
DATABASES['default'].update(db_from_env)

We use the binary version of psycopg2 as this is easier to install locally than the other version, which requires more libraries and other components, which make use of Postgresql. This also means that you don’t have to have Postgresql installed locally.

Add any of these libraries that you use to your requirements.txt file manually, or by re-running freeze.