New Wagtail Course! 🥳 The Ultimate Wagtail Developers Course

A Peek Inside Our Tech Stack

A small peak into the tech stack that's running this website.


Welcome to the insider's view of, where we're not just passionate about teaching Wagtail CMS but also about using the latest and greatest in web technology to deliver an exceptional learning experience. In this blog post, I'm excited to take you behind the scenes and share the modern tech stack that powers our platform. From the robust backend technologies like Wagtail 6.0, Django 5.0, and PostgreSQL 15, to the innovative use of ElasticSearch 8, Redis, Docker, and Docker Compose for seamless operation, we've pulled out all the stops. And that's not even touching on our sleek frontend, which is getting an upgrade with Tailwind CSS and, soon, interactive React components for lesson-specific comments. Whether you're a tech enthusiast, a fellow developer, or a curious learner, join me as we delve into the technologies that make a cutting-edge resource for web development education.

Below is a quick highlight of the services is using to run this website. (Note: we only need Python, Wagtail+Django and Postgres, the rest is gravy).

  • Postgres database
  • Wagtail 6.0 and Django 5.0
  • Python 3.10
  • Nginx
  • Redis
  • ElasticSearch
  • Docker and Docker Compose

Wagtail CMS

Diving into the heart of's backend, we're leveraging the dynamic duo of Wagtail and Django, a choice that's been instrumental in shaping this websites success. At the core it's using Wagtail 6.0 (which came out the week of this post) due to its simplicity in content management. This CMS doesn't just make it easy for us to curate and update course material; it makes the process downright enjoyable. No mysterious "what's this button do" situations arise like with other content management systems (*ahem* SquareSpace). Its flexibility is unmatched, allowing us to tailor the site to our specific needs without breaking a sweat.

But the benefits don't stop there. By building on top of Django 5.0, we harness all the power of this high-level Python web framework. Granted, we can't use Wagtail without Django since Wagtail was built on top of Django. This means robust security, rapid development, and a clean, pragmatic design — all of which contribute to a website that's not only easy to maintain but also scales beautifully as our community grows. The fact that both Wagtail and Django are actively worked on ensures that we stay on the cutting edge, with regular updates that keep our platform secure and efficient.

Why Wagtail?

Wagtail stands out from WordPress by offering a more tailored, flexible, and user-friendly experience for content management and web development. Unlike WordPress, which can often feel bloated due to its one-size-fits-all approach and reliance on plugins for extended functionality, Wagtail provides a sleek, efficient framework built on Django, giving developers the power of Python to create custom, scalable websites without compromise. Its intuitive editing interface is designed for efficiency, allowing content creators to focus on what they do best: crafting engaging content. Moreover, Wagtail's architecture supports a wide range of content types and integrates seamlessly with modern front-end technologies, making it the go-to choice for developers seeking a robust, secure, and highly customizable CMS that's both powerful and easy to maintain.

Wagtail CMS features

Inside Wagtail CMS, a suite of advanced features enables unparalleled customization and efficiency for web development. SnippetViewSets offer a modular approach to content management, allowing developers to reuse snippets of content across different parts of a site seamlessly. Hooks provide a powerful mechanism to extend Wagtail's functionality, enabling custom behaviours to be attached to different events within the CMS lifecycle. Orderables facilitate the creation of sortable lists of related items, making the management of complex data structures user-friendly and intuitive. StreamBlocks are at the core of Wagtail's flexible content creation, allowing developers and content editors to combine multiple blocks of various types (text, images, videos, etc.) within a single StreamField, ensuring rich, dynamic content. RoutablePages enable the definition of additional routes for a page, allowing for the creation of complex, custom page behaviours without the need for multiple templates or multiple Page models. Lastly, Custom Choosers extend Wagtail's built-in chooser interfaces, providing a tailored selection process for links, images, or any other type of referenceable content, thus enhancing the editorial experience by streamlining content creation and management. Together, these features make Wagtail CMS an incredibly powerful tool for building sophisticated, dynamic websites.

Most notably are Wagtail's new SnippetViewSets. They make managing extra non-Wagtail content (standard Django models) extra easy. Here's a small example that uses a `WagtailFilterSet` for additional future functionality.

``` # enrollments/ from wagtail.admin.filters import WagtailFilterSet from wagtail.snippets.models import register_snippet from wagtail.snippets.views.snippets import SnippetViewSet from .models import Enrollment class CourseFilter(WagtailFilterSet): class Meta: model = Enrollment fields = ["course"] @register_snippet class EnrollmentSnippetViewSet(SnippetViewSet): model = Enrollment icon = "user" filterset_class = CourseFilter list_display = ["user", "get_user_email", "course"] export_headings = { "user": "Username", "first_name": "First Name", "email": "Email", "course": "Course", } list_export = ["user", "get_user_email", "course"] export_filename = "enrollments" ```


In addition to its robust content management features, Wagtail CMS seamlessly integrates with leading external APIs such as Stripe and PayPal for secure and versatile payment processing, alongside YouTube and Vimeo for rich media embedding. This integration empowers developers to create dynamic, multimedia-rich websites that not only engage users with high-quality video content but also offer streamlined e-commerce capabilities. These API integrations enhance Wagtail's versatility, making it an ideal platform for a wide range of web projects that demand seamless external service integration.

PostgresSQL 15

When it comes to choosing the right database for, PostgreSQL 15 stands out as the backbone of our data storage and management. This decision was driven by several key factors that highlight PostgreSQL's superiority over simpler solutions like SQLite, especially for a platform of our scale and complexity.

Firstly, PostgreSQL's robustness and scalability are unparalleled. While SQLite is a fantastic lightweight database perfect for smaller projects and development environments, it falls short in handling the high concurrency and voluminous data requirements of a dynamic educational platform like ours. PostgreSQL 15, on the other hand, excels in these areas, offering advanced features such as multi-version concurrency control (MVCC), parallel query execution, and sophisticated indexing techniques. These capabilities ensure that as our user base grows and our data becomes more complex, our platform remains fast, reliable, and accessible.

Another significant advantage of PostgreSQL is its extensibility and support for advanced data types. Unlike SQLite, PostgreSQL supports a wide range of data types, including JSON/JSONB, which allows us to store and query our data more flexibly. This is particularly beneficial for managing the diverse content and user data on our platform, from lesson materials and user profiles to interactive comments and feedback. Furthermore, PostgreSQL's extension ecosystem, including powerful tools like PostGIS for geographical data and timescale for time-series data, provides us with the ability to easily expand our platform's capabilities as needed.

PostgreSQL's ArrayField is a pivotal feature for tracking "Course Progress" by providing an efficient way to store and manage lists of data within a single database column. By utilizing ArrayField, we can easily store user progress through courses, such as the IDs of completed lessons or modules, in a compact, searchable format. This not only simplifies the data model and speeds up queries related to a user's progress but also enhances the user experience by enabling real-time updates and insights into their learning journey, all managed within the powerful, relational structure of PostgreSQL. Here's an incredibly simple way of using an ArrayField:

``` from django.contrib.postgres.fields import ArrayField from django.db import models class Enrollment(models.Model): user = models.ForeignKey( '...' ) course = models.ForeignKey( '...', ) lessons_completed = ArrayField( models.SmallIntegerField(), blank=True, help_text='A list of LessonPage ID\'s that the user has viewed', default=list, ) ```

Lastly, PostgreSQL's active development community and consistent updates mean that we're always equipped with the latest features and security enhancements. This ensures that not only offers a secure learning environment but also stays ahead of the curve in leveraging database technology to enhance user experience and site performance.


Integrating ElasticSearch 8 into has significantly amplified the power of Wagtail's default search capabilities, transforming the way users interact with our content. Though it's not yet being used in an impactful way, it has definitely created better search results that can query through complex data.

By choosing ElasticSearch as our search backend, we've been able to offer our users lightning-fast search results, even as our repository of lessons, tutorials, and resources continues to expand. And it connects with Wagtail and Django beautifully, as in, the amount of code that was required to install and setup ElasticSearch as minimal.

Moreover, ElasticSearch's powerful analytics capabilities have given us deeper insights into how users engage with our content, enabling us to continuously refine and improve our offerings. The ability to analyze search trends and patterns helps us understand what our users are most interested in, guiding us in creating more targeted and relevant content.

In essence, ElasticSearch has supercharged our search functionality, making it more powerful, precise, and user-friendly. Don't get me wrong.. Wagtail's default search backend is incredibly powerful and intelligent. But ElasticSearch cranked that up and opened doors for much more complex search capabilities in the future.


Redis plays a crucial role in enhancing the performance and scalability of through its exceptional caching capabilities. As an in-memory data structure store, Redis offers lightning-fast access to cached data, significantly reducing the load on our primary database and speeding up response times for our users. We used to use file based caching for it's simplicity, but it turns out Redis was super simple to setup and add to Wagtail.

By implementing Redis for caching, we've managed to decrease page load times dramatically, making the browsing experience smoother and more enjoyable for everyone who visits the site. This improvement is particularly noticeable in frequently accessed pages and dynamic content.

Beyond just speed, Redis contributes to the efficiency of our platform by reducing the computational burden on our servers. By storing precomputed outputs, session data, and frequently queried information in Redis, we minimize the need for repetitive database queries. This not only frees up resources to handle more user requests simultaneously but also ensures our platform can scale more effectively to accommodate growth in traffic. The choice to use Redis for caching aligns perfectly with our commitment to delivering a high-quality, responsive learning environment on, proving that sometimes, the best solutions are those that work seamlessly behind the scenes, enhancing user experience without drawing attention to themselves.

Here's an example of how caching has drastically improved Below is some sample code for our LessonPage. In the Wagtail Admin, it numbers each page so editors know which page belongs in which order without having to re-order the pages. Caching saves us thousands of queries.

``` from django.core.cache import cache from wagtail.models import Page class LessonPage(Page): def get_child_order(self): """ Returns the order of this lesson in the admin. Because this requires so much iteration, the results are cached. """ order_number = cache.get(self.admin_display_cache_key) if order_number is None: siblings = self.get_siblings() order_number = 0 for sibling in siblings: order_number += 1 if == break # Cache the result cache.set(self.admin_display_cache_key, order_number, timeout=2592000) return order_number def get_admin_display_title(self): original_title = super().get_admin_display_title() order_number = self.get_child_order() return f"{order_number}. {original_title}" ```

Docker and Docker Compose

At, Docker stands at the forefront of our development and deployment strategy these days, serving as the cornerstone for creating perfectly identical environments across every stage of the workflow. This approach has effectively put an end to the all-too-common "works on my machine" conundrum, streamlining our development process and ensuring that we can focus on the real reason this website exists (teaching Wagtail CMS) rather than troubleshooting environmental discrepancies. The power of Docker lies in its containerization technology, which encapsulates our entire application along with its dependencies into isolated containers. This not only facilitates effortless upgrades of crucial components like Python, Postgres, Elasticsearch, and Redis but also simplifies the management of multiple Python versions.

  • Ever have to upgrade a production website that's using an outdated version of Python?
    It's painful.
  • Every have to upgrade a database version and hope everything works out?
    Us too.

Docker and Docker Compose removed those problems. Fun fact: New Wagtail CMS projects come with a Dockerfile.

The result is a development and production environment that's as reliable as it is efficient, allowing us to roll out updates and enhancements with unprecedented speed and confidence.

Docker Compose acts as the glue that binds our services together. By leveraging Docker Compose, we've transformed the setup of complex websites like from a potentially tedious and error-prone task into a streamlined, guesswork-free process. With a simple command (`docker-compose up --build -d`), Docker Compose orchestrates the deployment of all our services, ensuring they're interconnected and configured correctly right from the start. This capability not only accelerates the initial setup of local development environments but also ensures that our production deployments are smooth and predictable. The combination of Docker and Docker Compose has revolutionized the way we build, test, and deploy, making it possible to spin up complex, fully-integrated web environments in just a few minutes. This seamless integration of services means more time enhancing the platform and less time dealing with the intricacies of environment setup and maintenance.

Currently, the services being run in our `docker-compose.yml` file include:

  • Postgres database
  • Web backend
  • Nginx
  • Cerbot
  • Redis
  • ElasticSearch
  • Cron tasks

Do all of those things need to be containerized? Nope. But with a couple commands the entire project can be moved to another hosting provider or a different server and it just works.


On the front lines of, our approach to crafting a user-friendly, visually appealing interface hinges on two pivotal technologies: Tailwind CSS and the soon-to-be-integrated React for dynamic interactions. Behind all of this is the principle of simplicity.

The entire frontend ecosystem grows at a rapid pace. We are not interested in spending hundreds or thousands of hours on frontend development when 80% of the value that offers, technically, comes from the backend. That said, we aren't dead inside and absolutely require a beautiful and responsive website.

Wagtail + Tailwind CSS + (light) React.js = chefs kiss 👌

Tailwind CSS

Tailwind CSS has revolutionized the way we style our platform. As a utility-first framework, it provides us with an immense level of customization and flexibility, enabling us to design a unique, cohesive look with minimal effort. Its simplicity in setup and compatibility with Wagtail's templating engine has allowed us to streamline our development process, ensuring that the aesthetic and functional aspects of our site not only meet but exceed our users' expectations. Tailwind's responsive design features ensure that looks and performs beautifully across all devices, maintaining a seamless user experience that's both engaging and intuitive.

Most importantly, we don't want to constantly upgrade our frontend services, debug old problems, or waste time with finicky frontend setups. Simplicty is key. Python is simple. Wagtail is simple. And we believe the frontend should be simple, too. Tailwind gives us incredible power out-of-the-box, simplicty, and the ability to extend it (if needed).

The one caveat, which you'll find with any Django-based website, are RichText fields and forms. We did have to write a tiny bit of custom CSS for that. Here's an example for handling responsive nested images in Wagtail's RichTextField/RichTextBlock.

```css @screen md { .richtext-block img.richtext-image { @apply rounded-lg; } .richtext-block img.richtext-image.left { @apply float-left mr-4 mb-4 max-w-sm; } .richtext-block img.richtext-image.right { @apply float-right ml-4 mb-4 max-w-sm; } .richtext-block img.richtext-image.full-width { @apply w-full h-auto; } } .richtext-block img.richtext-image { @apply rounded-md; } .richtext-block img.richtext-image.left { @apply w-full h-auto; } .richtext-block img.richtext-image.right { @apply w-full h-auto; } ```


Looking ahead, we're excited about the integration of React into our platform, specifically to enhance the interactivity of lesson-specific comments and replies. React's component-based architecture offers a clean, efficient way to manage complex interactions within Django templates, eliminating the traditional complexities associated with dynamic content management. With React, we can instantly re-render parts of the Document Object Model (DOM) without needing to refresh the entire page. This means that users will be able to post comments, view replies, and engage with content in real-time, fostering a more interactive and community-driven learning environment. The combination of React's reactivity and efficiency with Tailwind CSS's styling capabilities promises to elevate the user experience on to new heights, making it more vibrant, responsive, and user-centric than ever before.

Prior to this all comments and replies were based on Django forms meaning any new comment or reply would reload the page, and also restart the video. That's not a lovely user experience.

Note: We are not using Wagtails v2 API for a headless website. We believe in simplicity and that would add additional overhead. Though we may enable the v2 API at some point, a full headless website is out of the question for us. The effort vs. output doesn't make sense.


When managing a website I always prefer to use Wagtail CMS. We made a solid effort to move onto a premium LMS (Learning Management System) this year, and we failed. Not because it was hard, but because it couldn't give us the flexibility we needed. That same flexibility is what Wagtail is known for.

With the same amount of effort we could have overhauled completely (as seen in this article) or we could have created half-baked, highly-limited frontend components on some other LMS. We decided to invest in ourselves and really crank up the volume with Wagtail. And we're so much happier because of it. 🎉

Kalob Taulien

Wagtail CMS enthusiast; Wagtail core team member; Coding teacher.

Get notified about new Wagtail content.