Back to home

Writing a Minimal Wagtail, Poetry & Node Dockerfile

Table of Contents

Over the last few months, I have worked extensively on several Wagtail projects. Off the back of this, I have decided to pivot from focusing on WordPress development in favour of Django and Wagtail. A big consideration that has come with this decision is managing the deployment of my projects. As a big Docker advocate, I decided to put together an optimised image for my workflow.

The Docker image for Wagtail, Poetry and Node

This image supports:

  • Python - you can use any version you like that is tagged up in Dockerhub.
  • Poetry
  • Node/NPM
# Python build stage for dependencies via Poetry
FROM python:3.13-slim AS pythonbuilder

RUN pip install poetry

ENV POETRY_NO_INTERACTION=1 \
    POETRY_VIRTUALENVS_IN_PROJECT=1 \
    POETRY_VIRTUALENVS_CREATE=1 \
    POETRY_CACHE_DIR=/tmp/poetry_cache

WORKDIR /app

COPY pyproject.toml poetry.lock ./
RUN touch README.md

RUN poetry install --without dev --no-root && rm -rf $POETRY_CACHE_DIR

# Node build stage for static assets
FROM node:20-slim AS nodebuilder

WORKDIR /app

COPY . .

RUN npm install

RUN npm run build

# Final runtime stage
FROM python:3.13-slim AS runtime

ENV VIRTUAL_ENV=/app/.venv \
    PATH="/app/.venv/bin:$PATH"

COPY --from=pythonbuilder ${VIRTUAL_ENV} ${VIRTUAL_ENV}

WORKDIR /app

COPY . .

# This is copying the files that I build with the npm run build command.
# You'll need to update these paths to suite your project.
COPY --from=nodebuilder /app/website/static ./website/static

RUN python manage.py collectstatic --noinput

EXPOSE 8000

ENTRYPOINT [ "sh", "entrypoint.sh" ]

How it works

The image utilises 3 stages:

  • pythonbuilder
  • nodebuilder
  • runtime

The Python build stage installs Poetry uses it to install the required packages. The Node build stage does the same for NPM, but then also runs npm run build to build the assets required for the site. Finally, the runtime stage then copies the packages from the Python build stage (minus Poetry and its dependencies) and copies the built assets from the Node build stage.

By utilising a multi-stage build, the final image size is really small, as it doesn’t include Poetry or Node and its dependencies. Beyond image size, this is also great for security, as we don’t have to be concerned with recent Node package exploits since it doesn’t exist in the final container, which runs in production.

Related blogs

  • django-testing-patterns
    Useful Django unit test patterns

    This is a collection of some common unit test patterns I find myself re-implementing across various projects. To save myself (and maybe some others) time in the future, I've decided to collect them all in one place.

    20 March 2026
  • writing-wagtail-streamfield-content-migrations
    Writing Wagtail StreamField content migrations

    When you drastically change one of your StreamFields and generate a migration for your new structure, the migration won't actually update what's already in your database. It'll describe the new, intended structure behind the fields, but the JSON data itself will remain exactly as it is.

    18 February 2026
  • directly-filtering-wagtail-pages
    Directly filtering Wagtail parent/child pages

    In Wagtail, pages don't have a typical, direct relationship like you'd expect in Django. Instead of using foreign keys to link them, each page has a `path` and a `depth` attribute...

    24 November 2025