Want to publish your Wagtail site in a Docker environment with PostgreSQL, Gunicorn and NGINX on a production server? The following tutorial will show you an easy way.
Prerequisite:
This tutorial is based on the previous article: Dockerize Wagtail & PostgreSQL as a development environment, which you should have read. To follow this tutorial it is necessary to have a working Docker environment including docker-compose installed on your development machine and on the production server and a fresh Wagtail project as described in the article.
Install Gunicorn
This is particularly simple. The following entry in requirements.txt installs Gunicorn when starting the environment.
gunicorn==20.0.4
Build the Docker production environment
To do this, we need to add the following files to our project:
.env.prod
.env.prod.db
docker-compose.prod.yml
app/entrypoint.prod.sh
app/Dockerfile.prod
Thus, the structure of the overall project should be as follows:
.
├── app
│ ├── app
│ ├── home
│ ├── search
│ ├── Dockerfile
│ ├── Dockerfile.prod
│ ├── entrypoint.prod.sh
│ ├── entrypoint.sh
│ ├── manage.py
│ └── requirements.txt
├── .env.dev
├── .env.dev.db
├── .env.prod
├── .env.prod.db
├── .gitignore
├── README.md
├── docker-compose.prod.yml
└── docker-compose.yml
The environment variables for web and database
Create .env.prod and .env.prod.db at the root level of your project and customize the information according to your production environment.
# .env.prod
DEBUG=False
SECRET_KEY=YOUR DJANGO SECRET KEY
DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 your-domain.com [::1]
SQL_ENGINE=django.db.backends.postgresql_psycopg2
SQL_DATABASE=demo_wagtail_prod
SQL_USER=demouserprod
SQL_PASSWORD=DemoPass
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
# .env.prod.db
POSTGRES_USER=demouserprod
POSTGRES_PASSWORD=DemoPass
POSTGRES_DB=demo_wagtail_prod
We need to adjust the Wagtail settings files according to the environment variables. For this we edit the settings/production.py as follows:
# app/app/settings/production.py
from .base import *
DJANGO_ROOT = '/home/app/'
SECRET_KEY = os.environ.get("SECRET_KEY")
DEBUG = os.environ.get("DEBUG")
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(" ")
# Database PostgreSQL
DATABASES = {
'default': {
'ENGINE': os.environ.get("SQL_ENGINE"),
'NAME': os.environ.get("SQL_DATABASE"),
'USER': os.environ.get("SQL_USER"),
'PASSWORD': os.environ.get("SQL_PASSWORD"),
'HOST': os.environ.get("SQL_HOST"),
'PORT': os.environ.get("SQL_PORT"),
}
}
try:
from .local import *
except ImportError:
pass
We also need an entrypoint sh-script for the production system that checks if the database has been started successfully and then performs some Django/Wagtail tasks (/app/entrypoint.prod.sh).
#!/bin/sh
if [ "$DATABASE" = "postgres" ]
then
echo "Waiting for postgres..."
while ! nc -z $SQL_HOST $SQL_PORT; do
sleep 0.1
done
echo "PostgreSQL started"
fi
python manage.py makemigrations --settings=app.settings.production
python manage.py migrate --settings=app.settings.production
python manage.py collectstatic --settings=app.settings.production --no-input --clear
python manage.py update_index --settings=app.settings.production
exec "$@"
Dockerize for production
To create the two Docker instances for our production environment, we still need the corresponding docker-compose and Docker files (app/Dockerfile.prod and docker-compose.prod.yml).
To allow NGINX to access the /media and /static directories later, we mount the volumes /var/www/app/static and /var/www/app/media on the server.
# app/Dockerfile.prod
# Pull the base image
FROM python:3.8.5-alpine3.12
# Create app directory
RUN mkdir -p /home/app
# Create app user
RUN addgroup -S app && adduser -S app -G app
# Create directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
RUN mkdir $APP_HOME/static
RUN mkdir $APP_HOME/media
WORKDIR $APP_HOME
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Install server packages
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev libffi-dev openssl-dev \
&& apk add jpeg-dev libwebp-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev libxml2-dev libxslt-dev libxml2
# Install Python packages
RUN pip install pip --upgrade
COPY ./requirements.txt $APP_HOME
RUN pip install -r requirements.txt
# Copy production entrypoint
COPY entrypoint.prod.sh $APP_HOME
# Copy project
COPY . $APP_HOME
# Chown all files
RUN chown -R app:app $APP_HOME
# Change user to app
USER app
# Run entrypoint.prod.sh
ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]
# docker-compose.prod.yml
version: '3.7'
services:
web:
restart: always
build:
context: app
dockerfile: Dockerfile.prod
command: gunicorn app.wsgi:application --bind 0.0.0.0:8009
volumes:
- /var/www/app/static:/home/app/web/static
- /var/www/app/media:/home/app/web/media
ports:
- 8009:8009
env_file:
- .env.prod
depends_on:
- db
db:
restart: always
image: postgres:12.2-alpine
volumes:
- postgres_data_prod:/var/lib/postgresql/data/
env_file:
- .env.prod.db
volumes:
postgres_data_prod:
Now we have created all the necessary files and we only have to add the production settings into app/app/wsgi.py
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings.production")
Build the Docker environment with:
docker-compose -f docker-compose.prod.yml build
Run the production with:
docker-compose -f docker-compose.prod.yml up -d
If everything went OK, you can test the new environment under http://127.0.0.1:8009
NGINX configuration
As a last step, we need to create a virtual host file for NGINX which acts as a load balancer for Gunicorn.
server {
client_max_body_size 100M;
listen 80;
server_name www.yourdomain.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8009;
}
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /var/www/app/static/;
}
location /media/ {
alias /var/www/app/media/;
}
}
And that's it. In just a few steps, we have a running Wagtail production system.
You can find the whole project ready to use on my GitHub repository:
phookycom / wagtailondocker
Cover image by Phooky.COM | Vector graphics by Macrovector | shutterstock 1741557098 & 1020152335