× Please consider supporting NoriPyt’s Wagtail Kickstarter to accelerate the development of this project.
July 29, 2015

Deploying Wagtail to Heroku

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.

Chris Rogers Chris Rogers Wagtail contributor

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 OS X 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:

pyvenv project_title
source project_title/bin/activate
cd project_title
pip install wagtail
pip install psycopg2

Here we are setting up the virtual env with the pyvenv command, then using 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 the database engine we'll be using.

We'll then 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 lets 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',
    }
}

We'll then finish off our local install by creating the 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 -

Using Python 3 on Heroku

By default, when your Heroku app is created it will use Python 2.7.9. 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.4.3

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 git repository

Before our app will work properly, we need to tell Heroku that we are going to be using our production settings. To do this we need to create a git repository and do an initial deploy to Heroku to get everything set up. First we'll create a .gitignore file with the following items in it:

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

We'll then initialise our repository, make our first commit, create our Heroku app, and push our repository up to Heroku.

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 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

Pushing configuration to 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='####' #Replace this with your own randomly generated, 50 character key

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:

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.

Now we'll push our settings up to Heroku. To do this we need to install a plugin first, then run the config:push command.

heroku plugins:install git://github.com/ddollar/heroku-config.git
heroku config:push

Finally, we need to add, commit and push our altered production.py settings file to Heroku.

git add .
git commit -m "Changing to use production settings"
git push heroku master

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)

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.

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'

Usually we would then just run the command python manage.py compress on the server to generate the assets, and all would be well. But Heroku doesn't quite work like that. You can log in to the Heroku command line and run compress, but when you leave that session those compressed files will be removed. Instead we need to take advantage of the hooks Heroku provides with its deployment process, and include a set of post-process commands to run during the deployment.

Luckily a kindly person has already created a set of scripts to do everything you need, called the heroku-django-cookbook. Simply copy the bin file from the heroku-django-cookbook repo into your project root.

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!

Blog

Copyright © 2018 Torchbox Ltd