Dockerizing Wagtail App

Table of Contents

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

Objectives

By the end of this chapter, you should be able to:

  1. Understand Docker Compose and the benefits.
  2. Use Docker Compose to create and manage Wagtail, Postgres services, and do development.

Install Docker Compose

Docker Compose is a tool for defining and running multi-container Docker applications. It uses YAML files to configure the application's services and performs the creation and start-up process of all the containers with a single command.

The docker-compose CLI utility allows users to run commands on multiple containers at once, for example, building images, scaling containers, running containers that were stopped, and more.

First, please download and install Docker Compose if you haven't already done so.

$ docker --version
Docker version 20.10.11, build dea9396

$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c

Config File Structure

Let's start with our config file structure, this can help you better understand the whole workflow:

├── compose
│   └── local
│       └── web
│           ├── Dockerfile
│           ├── entrypoint
│           └── start
├── docker-compose.yml
├── manage.py
├── wagtail_blog
└── requirements.txt

You will see we have config files docker-compose.yml and some files in compose directory, you do not need to create them for now. I will talk about them with more details in the coming sections.

Note: The config file structure come from cookiecutter-django, which is a great project for people who want to learn Django.

Compose file

Compose file is a YAML file to configure your application's services.

When you run docker-compose command, if you do not specify Compose file, the default file is docker-compose.yml, that is why we create docker-compose.yml at root directory of Django project, because it can save us time when typing command during development.

Let's add docker-compose.yml

version: "3.7"

services:
  web:
    build:
      context: .
      dockerfile: ./compose/local/web/Dockerfile
    image: wagtail_blog_web
    command: /start
    volumes:
      - .:/app
    ports:
      - 8000:8000
    env_file:
      - ./.env/.dev-sample
    depends_on:
      - db

  db:
    image: postgres:14.0-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - POSTGRES_DB=wagtail_blog
      - POSTGRES_USER=wagtail_blog
      - POSTGRES_PASSWORD=wagtail_blog

volumes:
  postgres_data:

Notes:

  1. Here we defined two services, one is web (django), the other one is db
  2. We create a named docker volume postgres_data, and use it to store the db data, so even db container is deleted, the db data can still exist.

Environment Variables

We can put env variables in a specific file for easy management.

Let's create .env directory, and add .dev-sample file

SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=wagtail_blog
SQL_USER=wagtail_blog
SQL_PASSWORD=wagtail_blog
SQL_HOST=db
SQL_PORT=5432

Please make sure .env is not excluded in the .gitignore, so it can be added to Git repo

Please note that the db login credential should match environment variables of db service in docker-compose.yml

Next, let's update DATABASES of wagtail_app/settings.py to read env variables.

import os

DATABASES = {
    "default": {
        "ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
        "NAME": os.environ.get("SQL_DATABASE", os.path.join(BASE_DIR, "db.sqlite3")),
        "USER": os.environ.get("SQL_USER", "user"),
        "PASSWORD": os.environ.get("SQL_PASSWORD", "password"),
        "HOST": os.environ.get("SQL_HOST", "localhost"),
        "PORT": os.environ.get("SQL_PORT", "5432"),
    }
}

Dockerfile

In Docker Compose, we can let it create docker container from existing docker image or custom docker image.

To build custom docker image, we need to provide Dockerfile

Please create directory and file like this

├── compose
│   └── local
│       └── web
│           ├── Dockerfile

Edit compose/local/web/Dockerfile

FROM python:3.10-slim-buster

ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1

# Install system packages required by Wagtail and Django.
# https://github.com/wagtail/wagtail/blob/v4.0.2/wagtail/project_template/Dockerfile
RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
    build-essential \
    libpq-dev \
    libmariadbclient-dev \
    libjpeg62-turbo-dev \
    zlib1g-dev \
    libwebp-dev \
 && rm -rf /var/lib/apt/lists/*

# Requirements are installed here to ensure they will be cached.
COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt

COPY ./compose/local/web/entrypoint /entrypoint
RUN sed -i 's/\r$//g' /entrypoint
RUN chmod +x /entrypoint

COPY ./compose/local/web/start /start
RUN sed -i 's/\r$//g' /start
RUN chmod +x /start

WORKDIR /app

ENTRYPOINT ["/entrypoint"]

Notes:

  1. PYTHONDONTWRITEBYTECODE=1 tell Python to not write bytecode (.pyc) and __pycache__ directory on local env.
  2. RUN sed -i 's/\r$//g' /entrypoint is used to process the line endings of the shell scripts, which converts Windows line endings to UNIX line endings.
  3. In the above docker-compose.yml, we config docker volume .:/app, so here we set WORKDIR /app. If we edit code on host machine, then the code change can also been seen in /app of the docker container.

Next, let's check the entrypoint and start script.

Entrypoint

In docker-compose.yml, we can use depends_on to let web service run after db service. However, it can not guarantee web service start after db service is trully ready. (Github Issue)

So we can add script in entrypoint to solve this problem.

compose/local/web/entrypoint

#!/bin/bash

set -o errexit
set -o pipefail
set -o nounset

postgres_ready() {
python << END
import sys

import psycopg2

try:
    psycopg2.connect(
        dbname="${SQL_DATABASE}",
        user="${SQL_USER}",
        password="${SQL_PASSWORD}",
        host="${SQL_HOST}",
        port="${SQL_PORT}",
    )
except psycopg2.OperationalError:
    sys.exit(-1)
sys.exit(0)

END
}
until postgres_ready; do
  >&2 echo 'Waiting for PostgreSQL to become available...'
  sleep 1
done
>&2 echo 'PostgreSQL is available'

exec "$@"
  1. We defined a postgres_ready function which is called in loop. The loop would only stop if the db service is able to connect.
  2. The last exec "$@" is used to make the entrypoint a pass through to ensure that Docker container runs the command the user passes in (command: /start, in our case). For more, check this Stack Overflow answer.

Start script

Now, let's add start script.

compose/local/web/start

#!/bin/bash

set -o errexit
set -o pipefail
set -o nounset

python manage.py migrate
python manage.py runserver 0.0.0.0:8000

Start application

Let's update requirements.txt to include postgres dependency

psycopg2-binary==2.9.4        # new

Building the docker images:

$ docker-compose build

Once the images are build, start the application in detached mode:

$ docker-compose up -d

# check realtime logs
$ docker-compose logs -f

web_1  | Starting development server at http://0.0.0.0:8000/
web_1  | Quit the server with CONTROL-C.

This will start containers based on the order defined in the depends_on option. (db first, web second)

  1. Once the containers are up, the entrypoint scripts will execute.
  2. Once Postgres is up, the respective start scripts will execute. The Django migrations will be applied and the development server will run. The Django app should then be available.

You can check the docker compose application with this command.

$ docker-compose ps

        Name                       Command              State           Ports
--------------------------------------------------------------------------------------
wagtail_project_db_1    docker-entrypoint.sh postgres   Up      5432/tcp
wagtail_project_web_1   /entrypoint /start              Up      0.0.0.0:8000->8000/tcp

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on testdriven.io.

He is also the founder of the AccordBox which provides the web development services.

Django SaaS Template

It aims to save your time and money building your product

Learn More

Build Jamstack web app with Next.js and Wagtail CMS.

Read More
© 2018 - 2024 AccordBox