Fix WeasyPrint "Cannot Load Library" for Django PDF Generation in Docker
Fix WeasyPrint OSError cannot load library gobject-2.0-0, libpango, or libharfbuzz when generating PDFs in Django on Docker, Heroku, or a slim server: install Pango and fonts, use the right package names, and keep PDF rendering in a worker.
Problem statement
Invoice or report PDFs work locally, but in production the view crashes:
OSError: cannot load library 'gobject-2.0-0': gobject-2.0-0: cannot open shared object
file: No such file or directory. Additionally, ctypes.util.find_library() did not
manage to locate a library called 'gobject-2.0-0'
or names libpango-1.0-0, libharfbuzz-0, or libfontconfig. WeasyPrint is a Python package, but it renders text and layout with Pango, HarfBuzz, and fontconfig, which are C libraries from the operating system. Slim Docker images and minimal servers do not include them, and pip install weasyprint cannot install them.
- Debian or Ubuntu image:
apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libfontconfig1. - Add fonts:
fonts-dejavu-coreor the fonts your templates use; without them PDFs render with boxes or fallback fonts. - On Alpine:
apk add pango fontconfig ttf-dejavu, and expect larger effort than on Debian. - On Heroku, add the system libraries with an Apt buildpack or deploy a Docker image.
- Generate large PDFs in a Celery task, not in the request.
Step-by-step solution
1. Install the system libraries
For python:3.12-slim (Debian):
FROM python:3.12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libharfbuzz-subset0 \
libfontconfig1 fonts-dejavu-core \
&& rm -rf /var/lib/apt/lists/*
WeasyPrint's documentation lists the exact packages per distribution and version; recent versions no longer need Cairo or GDK-Pixbuf. Check with:
docker run --rm myapp python -c "import weasyprint; print(weasyprint.__version__)"
Importing WeasyPrint loads the libraries, so a successful import confirms the fix.
2. Add the fonts your templates use
If your CSS asks for font-family: Inter, the font must be installed in the image or loaded with @font-face from a file WeasyPrint can read. Copy fonts into the image and refresh the cache:
COPY fonts/ /usr/share/fonts/truetype/app/
RUN fc-cache -f
Or reference them in the PDF stylesheet with an absolute file:// URL or through Django static files using a base_url when rendering:
HTML(string=html, base_url=request.build_absolute_uri("/")).write_pdf()
3. Handle Heroku and other buildpack platforms
Heroku's stack images include some libraries, but versions and availability change between stacks. The reliable options are the heroku-community/apt buildpack with an Aptfile listing the packages, or a container deploy with your own Dockerfile.
4. Keep rendering out of the request
PDF rendering is CPU-heavy and can take seconds for multi-page documents. Long renders tie up a Gunicorn worker and may hit timeouts. Render in a Celery task, store the file in media storage, and send or show a link:
@shared_task
def render_invoice_pdf(invoice_id):
invoice = Invoice.objects.get(pk=invoice_id)
html = render_to_string("invoices/pdf.html", {"invoice": invoice})
pdf = HTML(string=html, base_url=settings.SITE_URL).write_pdf()
invoice.pdf.save(f"invoice-{invoice.number}.pdf", ContentFile(pdf))
5. Check memory
Large documents and images use a lot of memory during layout. Workers that render PDFs may need more memory than web workers; run them as a separate Celery queue with fewer concurrent tasks.
Explanation
WeasyPrint calls Pango and HarfBuzz through cffi, loading them by library name at import time. If dlopen cannot find them, Python raises OSError: cannot load library, naming the first one it tried, often gobject-2.0-0 because GLib is a dependency of Pango. Development machines typically have these through a desktop environment or Homebrew; containers and servers built for size do not. Fonts are found through fontconfig at render time, and a missing font silently falls back to whatever is available.
Edge cases / notes
- Old WeasyPrint versions. Versions before 53 used Cairo and needed more libraries. Upgrading simplifies dependencies.
- Alternative tools. Headless Chromium (via Playwright) renders PDFs too and needs its own large set of libraries.
- Security. Do not render user-supplied HTML with network access; a
url_fetcherthat blocks external URLs prevents server-side request forgery.
Next steps
- Build a dependable image with Dockerize Django for Production.
- Run renders in workers with Configure Redis and Celery for Django in Production.
- Similar system-library issue for maps: Fix GeoDjango Could Not Find the GDAL Library.
FAQ
Why does pip install weasyprint not fix it?
Pip installs the Python package only. Pango, HarfBuzz, and fontconfig are system libraries installed with apt or apk.
Which fonts should I install?
The ones your PDF CSS uses, plus a fallback such as DejaVu. Copy brand fonts into the image.
Does WeasyPrint work on Alpine?
Yes, with pango and fonts installed via apk, but Debian-based images are usually simpler.
Should PDFs be generated in the view?
Small single-page PDFs can be. Larger documents belong in a background task.