Get started

Tutorials

18 Jan 2017

Deploying Wagtail to Heroku, 2017 edition

Heroku is a great platform which takes away much of the hassle of system administration when trying to deploy a website, but it has its idiosyncrasies which you need to know about when trying to deploy Wagtail.

Kyle Rutten

Kyle Rutten

This is an update to Chris Rogers's tutorial from July 2015.

By the end of this tutorial you'll have a simple, working implementation of Wagtail running on Heroku.

Before we start though, I'm making the following assumptions:

  1. You have a Heroku account, and you've followed its basic installation instructions.
  2. You have a version of Python 3 installed. That's not essential, but it's good to be using modern technology. If you haven't, I'd recommend using Homebrew.
  3. This tutorial is written for the perspective of an macOS user. If you're using a flavour of Linux then the process will be very similar. If you're using Windows, you might run into some differences, I don't know.

Setting up your virtual environment

We're going to use Python 3's built in virtual environment functionality. Run the following commands in your terminal:

virtualenv -p python3 project_title
source project_title/bin/activate

By default, macOS comes with Python 2.7, hence the necessity to use virtualenv as opposed to venv. At this point I like to double check and make sure my virtual environment is running Python 3. You should now be seeing (project_title) as the first words on your current command line. Simply run the command:

python --version

This should return:

Python 3.6.0

(As of the time of this writing)

If not, double check the steps in this section. If everything looks good then enter the following commands:

cd project_title
pip install wagtail
pip install psycopg2

So far we have set up the virtual environment with the virtualenv command, then used the source command to activate it.

We're then using pip to install the latest official release of Wagtail. We also take the opportunity to install PostgreSQL, which is Heroku’s native database engine.

Now we'll create our Wagtail app within the virtual environment and cd into the new directory.

wagtail start project_title
cd project_title

Using Postgres

Heroku is optimised to use PostgreSQL with Django, so let's change our database engine in base.py from SQLite to PostgreSQL by replacing the existing DATABASES section with the following:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'project_title_db',
    }
}

Don’t forget to change project_title_db to your project’s database name. We'll be creating your database next, so be sure to keep the name consistent with what you set here.

Now we'll finish off our local install by creating that project database, and performing an initial database migration. Run the following commands in your terminal:

createdb project_title_db
./manage.py migrate
./manage.py createsuperuser
./manage.py runserver

You should then be able to see the 'Welcome to Wagtail' screen by going to localhost:8000 in your browser. Go to localhost:8000/admin, and you should be able to log in with the user you created in the createsuperuser stage.

Adapting for Heroku

The steps we have taken so far are the basic installation steps for Wagtail in any circumstances. However, if we now want to deploy this implementation to Heroku, we need to make some specific changes.

Django-toolbelt

The first thing you need to do is install the various requirements that Heroku looks for when hosting a Django application. These all come together in a pip package called django-toolbelt. We'll install this, and then use the pip freeze command to update our requirements.txt file.

pip install django-toolbelt
pip freeze > requirements.txt

Create your Procfile

Heroku requires you to create a file called Procfile, in which you declare the commands to be run by your application's Dynos. Your Procfile should be located in the root directory of your app, the same directory in which your manage.py file is located. In this case you only need one line in this file:

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

Don’t forget to change project_title to your project’s name.

Using Python 3 on Heroku

By default, when your Heroku app is created it will use Python 2.7.13. However, we want our app to use Python 3. We can define a new 'run time' for Heroku, by creating a runtime.txt file in our root project directory. In this file, just add the exact version of Python that you want to use as below.

python-3.6.0

(At the time of this writing)

Updating our settings for Heroku

As our Heroku app will be accessible to the rest of the web, we'll want to use our production settings rather than our local settings. Our local settings have Debug=True set, and utilise Django's built in static file serving mechanism which is not secure.

First of all, we'll add the following settings into our production.py settings file. These new settings firstly allow us to take advantage of Heroku's PostgreSQL plugin. The ALLOWED_HOSTS setting is necessary to allow Heroku to run our app at the auto-generated domain that it will create.

# Parse database configuration from $DATABASE_URL
import dj_database_url
DATABASES['default'] =  dj_database_url.config()
	
# Honor the 'X-Forwarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Allow all host headers
ALLOWED_HOSTS = ['*']

Creating a .gitignore File

Before our app will work properly, we need to tell Heroku that we are going to be using our production settings. First we'll create a .gitignore file with the following items in it:

*.pyc
.DS_Store
*.swp
/venv/
/static/
/media/
.env

Preparing Configuration for Heroku

We want to keep our production settings as secure as possible, so we don't want to keep them in our git repository. Instead, we'll create a special file that will convert some of our sensitive settings into environment variables that we can reference in our production.py file.

Firstly we'll create a new file, called .env, in our root project directory. You'll remember that we already added a .env entry into our .gitignore file earlier in the process.

In the .env file, add the following lines

DJANGO_SETTINGS_MODULE=project_title.settings.production
SECRET_KEY='####'

Don’t forget to change project_title to your project’s name and the hashes in SECRET_KEY to the 50 character key found in your dev.py file.

The first line specifies that Heroku should utilise your production settings file. The second is the secret key for your production site. We want to reference this from outside our production.py file for the sake of security.

To utilise these settings, add in the following lines to the top of your production.py settings file:

from __future__ import absolute_import, unicode_literals

import os

env = os.environ.copy()
SECRET_KEY = env['SECRET_KEY']

First we are assigning the environment variable object to a local variable, then referencing the SECRET_KEY environment variable in the actual settings.

Creating a Git Repository and Pushing Configuration to Heroku

We're almost ready for Heroku! We need to create a git repository and do an initial deploy to Heroku to get everything set up. We’ll initialise our repository, make our first commit, create our Heroku app, and push our repository up to Heroku. Then we’ll install a plugin to allow us to push up our settings, run the plugin, migrate and create a user.

git init
git add .
git commit -m "first commit to heroku"
heroku create 
# Creates a new Heroku app and connects it to your initialised git repo
git push heroku master 
# Pushes your commited code up to your new ap
heroku plugins:install heroku-config
# Install plugin that allows us to push settings to heroku
heroku config:push
# Pushes settings to heroku, if you get an error check your spaces.
heroku run python manage.py migrate 
# Heroku allows you to run shell commands remotely with the 'heroku run' command.
heroku run python manage.py createsuperuser
# Creates a new superuser on Heroku
heroku ps:scale web=1 
# Ensures that a new Dyno is running for your project

Congratulations! Your Wagtail app is now deployed to Heroku!

Now open up your app in a browser by using the heroku open command, and navigate to the Wagtail admin. But what's this? No CSS, images, or static assets of any kind? That is because Django cannot serve static assets in a production environment on its own.

Enter Whitenoise!

Serving static assets on Heroku

Whitenoise is a Python library that allows Django to serve its own static files without relying on a CDN like Amazon S3. First of all, let's install the package and add it to our requirements.txt file.

pip install whitenoise
pip freeze > requirements.txt

Then we'll tell Heroku that we want to use it by changing our wsgi.py file to read as follows:

import os
from django.core.wsgi import get_wsgi_application
from whitenoise.django import DjangoWhiteNoise

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_title.settings")

application = get_wsgi_application()
application = DjangoWhiteNoise(application)

Note: the above is for Whitenoise 3. If you are using Whitenoise 4, the wsgi integration has been removed. Instead you should add Whitenoise to your middleware list. See v4.0 breaking changes

Don’t forget to change project_title to your project’s name.

We'll use Whitenoise's gzip functionality, and activate the offline compression, which is required to generate the admin section assets. Add the following code into your production.py file.

# For Whitenoise 4 use 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'


COMPRESS_OFFLINE = True
COMPRESS_CSS_FILTERS = [
    'compressor.filters.css_default.CssAbsoluteFilter',
    'compressor.filters.cssmin.CSSMinFilter',
]
COMPRESS_CSS_HASHING_METHOD = 'content'

Finally add, commit and push your code again, and that's it! If you navigate to the Wagtail admin you should now be seeing static assets in all of their glory!

While Whitenoise is a handy solution for static assets, it is not intended to handle media assets. There are a few reasons for this. The main reason being that it only checks for files at startup so anything uploaded by a user won’t be seen. It could also open your project up to security risks if you use Whitenoise for media files. Amazon's S3 service is perfect for this.