Deploying a Wagtail site to Fly.io Part 2: Configuration

This post is part of a series:

  1. The Plan
  2. Configuration
  3. Dockerising & Requirements
  4. Static & Media Files
  5. Deploying

To kick everything off, you’ll need a Wagtail site configured in a local development environment. The Wagtail Getting Started guide is a great place to start.

Alternatively, grab a checkout of bakerydemo, or find your own existing project.

We’ll start by getting your application in to a state where it is easily configurable with environment variables.


When building an application for any platform, following the Twelve-Factor App methodology is highly recommended for reasons that are well explained on the linked site.

In particular, factor 3 (‘Config’) is about storing configuration that is likely to vary on the host, rather than in your codebase.

The word ‘environment’ is a bit overloaded. It can mean both ‘an environment something runs on’ e.g. Fly, Heroku, a Docker container etc, or it can mean ‘a system’s environment variables’.

I’ll refer to the former as a ‘host’ for simplicity here, even though that could be a local dev setup, CI, or something else.

If you’ve seen a few Django projects, you’ve probably seen projects with multiple configuration files like prod.py, dev.py, etc. This can create a tight coupling between your codebase and the host it runs on. Rather than your host being able to inform the application about things like database credentials, domains and secrets, the codebase needs to be ‘aware’ of the details of the host it’s going to run on before it gets there!

Wagtail’s default starter template is guilty of this, but we can focus on the settings/base.py, treating it as our only settings file and varying any settings that might change with environment variables.

While there are a lot of nice fully-featured packages that help you do this (django-environ, Dynaconf, python-decouple), we’ll start simple by pulling values directly from the environment using Python’s os module.

There’s a few settings we’ll want to convert to environment variables (or add in if they don’t exist). I’ll usually end up with something like:

import os

DEBUG = os.getenv("DEBUG", "false").lower() == "true"
# ...
SECRET_KEY = os.getenv("SECRET_KEY")
# ...
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")

This way, the hosts’ environment variables specify:

The other important thing that we need to pull from the environment are the database credentials. In 12-factor apps, these are often expressed in a URL format (e.g. postgres://username:password@host:5432). This can take a bit of parsing to get the separate values Django expects, so a package like dj-database-url is very useful here.

Configuring Django to use a DATABASE_URL environment variable using this package can be done by replacing your DATABASES settings value with:

import dj_database_url

# ...

DATABASES = {
    "default": dj_database_url.config(conn_max_age=600)
}

Other things that vary can include:

Most of these values can be easily parsed from the simple strings we are limited to with environment variables.

🍞

We don’t need to make any immediate configuration changes to bakerydemo - it’s already in a 12-factor-enough state for us to work with, and includes dj-database-url. You can take a closer look at production.py to see a few more examples of settings being pulled from the environment.


On to the complicated bit…