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.
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:
- You have a Heroku account, and you've followed its basic installation instructions.
- 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.
- 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.