This will show you how to configure your Dokku server so you can deploy a Wagtail site to it. We'll assume you have a working Wagtail website and a working Dokku installation.

We'll also assume you've created your Wagtail site using the wagtail start command, per the instructions on wagtail.io. If you're dealing with a more complicated site, check out our past tutorial on deploying Wagtail sites.

1. Add Dokku files to your project

First off, appreciate the fact that when you deploy to Dokku, you are basically chucking random code at it. Whether it's Python, Node.js, Ruby, or even C++, Dokku can handle it. That said, Dokku is not as smart as you. You need to give it clues about how to deal with your project.

With that in mind, your Wagtail project will need these files at minimum in the root of your repository:

  • requirements.txt
  • runtime.txt
  • Procfile

requirements.txt

This is a pretty common file for Python projects. When you have this, it will signal to Dokku, "Hey, this is a Python project. Treat it like such." More specifically, it triggers the Python buildpack which installs Python and all of the packages specified in your requirements.txt on each deploy.

The wagtail start command should have generated this for you. We've added two more packages, whitenoise and gunicorn. Please click the links and make sure to use the latest versions! These extra packages will be explained more below.

Django>=2.0,<2.1
wagtail>=2.2,<2.3

whitenoise==4.1
gunicorn==19.9.0

runtime.txt

The Python buildpack uses Python 2 by default. If you supply a runtime.txt it'll use the version you write inside. For example:

python-3.7.0

Please actually check the full list of supported runtimes first, though. This article could be outdated.

Procfile

Okay, now we need to tell Dokku how to actually run our application. You're probably used to using python manage.py runserver from your command line, but keep in mind that this command runs Django's development server. Let's use gunicorn instead, which is a suitable production server. Contents of our Procfile:

web: gunicorn <mysite>.wsgi -b $HOSTNAME:$PORT

It's okay if you don't understand this. Just replace <mysite> with your project name and leave the rest alone.

2. Play nice with Dokku

Once you have all the necessary files, there are still some Dokku quirks you need to adapt for.

Make logging work

Don't skip this step. Without it, you won't be able to debug anything. Basically, you need Wagtail to output errors into the console so you can run dokku logs <myapp> on your app and actually see the errors.

Edit mysite/settings/production.py and add this code to it:

# Logging

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'ERROR'),
        },
    },
}

Load static files (CSS, images, etc)

In production mode, Wagtail won't serve your CSS (or anything else in a static folder). The simple solution is to add whitenoise to your project.

Edit mysite/settings/base.py and add whitenoise to the MIDDLEWARE list, just below Django's security middleware. It should look something like this:

MIDDLEWARE = [
  # ...
  'django.middleware.security.SecurityMiddleware',
  'whitenoise.middleware.WhiteNoiseMiddleware',

  'wagtail.core.middleware.SiteMiddleware',
  # ...
]

You'll also want to add whitenoise to your INSTALLED_APPS so it can work the same in development. Place it towards the bottom, just above Django's staticfiles app like so:

INSTALLED_APPS = [
	# ...
    'whitenoise.runserver_nostatic',
    'django.contrib.staticfiles',
]

Feel free to review the complete guide to using whitenoise with Django, but you should be good to go now.

Prepare for environment variables

There are some things you don't want to check into your git repository. These things are secrets that you will handle on the server with Dokku. Let's prepare our project to accept these.

Edit mysite/settings/production.py and add this code to it:

import os
env = os.environ.copy()


if 'SECRET_KEY' in env:
    SECRET_KEY = env['SECRET_KEY']

if 'ALLOWED_HOSTS' in env:
    ALLOWED_HOSTS = env['ALLOWED_HOSTS'].split(',')

Check our example production.py.

Delete the Dockerfile

Wagtail's wagtail start command comes with a Dockerfile in the root of the project. Please delete this file as it will confuse Dokku.

Add a .gitignore

There are some things you won't want to check into the git repo, so add a file called .gitignore to the project root to tell git not to track these files. I suggest using this to start:

*.log
*.pot
*.pyc
__pycache__/
db.sqlite3
media
/static
/mysite/settings/local.py

Replace mysite with your project name on the bottom line.

Commit your code so far

Don't forget that Dokku relies on git to read the files. If you don't commit these files, Dokku won't see them!

If you don't have a repo set up yet, run this command to create one:

$ git init

Then commit your code.

$ git add --all
$ git commit -m "Prepared for Dokku"

3. Configure the server

Now we're ready to shell into the server and configure Dokku to accept your site! First, create the app.

$ dokku apps:create mysite

Basic configuration

Now we have to configure the app with secrets. These are obviously things we don't want the world to see, so we'll configure them on the server itself rather than inside of your settings files.

$ dokku config:set <mysite> ALLOWED_HOSTS=mysite.com
  • ALLOWED_HOSTS - Have you configured Dokku to use your domain name? If not, put the server's IP address here instead. You'll have to reconfigure this value once you set up the domain name.
$ dokku config:set <mysite> DJANGO_SETTINGS_MODULE=<mysite>.settings.production
  • DJANGO_SETTINGS_MODULE - This is the name of the settings module. Replace <mysite> with your real project name.
$ dokku config:set <mysite> SECRET_KEY='GENERATE_ME'
  • SECRET_KEY - Your site must have a unique secret key. You can generate one.

Mount the database

Every time you restart or deploy a Dokku app, the entire filesystem is completely destroyed. Yes, blown into smithereens! This is a good thing because it reduces the chance of bugs by always starting with a fresh environment every time, but you'll need to tell Dokku which files to keep. db.sqlite3 is pretty important, since it's your entire database.

Let's mount it.

$ mkdir /home/dokku/<mysite>/storage
$ touch /home/dokku/<mysite>/storage/db.sqlite3
$ chown -R dokku:dokku /home/dokku/<mysite>/storage
$ dokku storage:mount <mysite> /home/dokku/<mysite>/storage/db.sqlite3:/app/db.sqlite3

Replace <mysite> here with the name of your Dokku app in all cases.

Deal with media

File uploads have the same issue. You want them to persist between deploys. Like static files, Wagtail also refuses to serve them in production. This is because there is a security concern with letting people upload files to your server. Usually you want to host file uploads in a separate server entirely. The risk is a malicious file being used to leak your database or shut down your site.

That said, you might be willing to take this risk if it's a small site where only a select few people can upload things. Follow these steps to make file uploads work.

This part of the tutorial is dirty. You might run into issues. If so, I'm sorry. It's complicated, and short of explaining what every individual command does in detail, there's not much more I can do. If you get stuck, you can always skip this step and come back later.

First, we need to mount the media directory as we did the database. Replace <mysite> with your Dokku app name in all the command below.

$ mkdir /home/dokku/<mysite>/storage/media
$ chown -R dokku:dokku /home/dokku/<mysite>/storage
$ dokku storage:mount mysite /home/dokku/<mysite>/storage/media:/app/media

This will allow file uploads to persist between deploys. We still need to make these file uploads visible on the public website, though.

$ mkdir -p /home/dokku/<mysite>/nginx.conf.d
$ nano /home/dokku/<mysite>/nginx.conf.d/media.conf

This will open a text editor, where you'll want to paste this:

location /media/ {
    alias /home/dokku/<mysite>/storage/media/;
}

Press ctrl+x, Y, enter to save your changes. Finally, set permissions on the files you just made.

$ chown -R dokku:dokku /home/dokku/<mysite>/nginx.conf.d

Increase max upload size

By default, you'll be able to upload only very small files. Use the command below to increase the file upload size to 100MB.

$ echo 'client_max_body_size 100M;' > /home/dokku/<mysite>/nginx.conf.d/upload.conf
$ chown -R dokku:dokku /home/dokku/<mysite>/nginx.conf.d

3. Deploy

We are very close to being done now! On your local machine, add the Dokku remote to your repo and push it.

$ git remote add dokku dokku@<mydomain>:<mysite>
$ git push dokku master

If it doesn't fail, that's something! Visit the URL you get at the very end and see what you get. If you get some variation of remote rejected, you'll want to read the error output and see if you can figure out what went wrong.

Migrate

You need to migrate the production database manually any time you push code with migrations. On the production server, run this:

$ dokku run <mysite> python manage.py migrate

Create a new Wagtail admin user

Assuming everything went fine, you can create a new admin account for yourself with (on the production server):

$ dokku run <mysite> python manage.py createsuperuser

Afterwards, visit mysite.com/admin and log in!

Pushing up an existing database

If you want to use an existing database that you have locally, run this from the local project on your computer:

$ scp db.sqlite3 root@<mydomain>:/home/dokku/<mysite>/storage/

Configure your domain name

Going through this in detail is about outside the scope of this article, but be sure to check out Dokku's documentation on domains.

Troubleshooting

Hopefully this all worked, but if not, you can always check the logs. In your production server, run:

$ dokku logs <mysite>

This should help you determine what's wrong. You can also check out this example repo I created to accompany this post.

If you hit a wall you can't solve (or you notice I'm missing any steps), please email me at alex@candlewaster.co.