Managed containers / Docker
WeasyPrint on Docker: a Pango version that does not match
It installs and runs, and the text is subtly wrong: incorrect kerning, broken ligatures, or an exception about a symbol that is not found in a shared library.
What is actually wrong
WeasyPrint calls Pango, Cairo and HarfBuzz through cffi, binding to symbols by name at import time. The versions of those libraries are decided by the base image's distribution release, not by pip. A recent WeasyPrint on an older stable base finds an older Pango, and the mismatch shows up as bad text shaping or a missing symbol rather than as a dependency error.
What Docker gives you to work with
an image you build, which means every system library and font is present only because you put it there.
- Packaging: no limit, but image size is a real cost: it is pulled on every node that runs it and it is rebuilt on every deploy.
- Filesystem: fully writable. The constraint that surprises people is /dev/shm, which defaults to 64 MB and is not the same thing as the container's memory limit.
- Processes: full control, with one catch: the process you start is PID 1, and PID 1 does not reap orphaned children unless it is written to. Browser processes accumulate as zombies.
The fix, if you are keeping WeasyPrint
Pin both halves. Choose the base image release deliberately, and pin the WeasyPrint version to one whose requirements that release satisfies. Treat the base image tag as part of the dependency specification rather than as a detail.
# The distribution release decides the Pango version, so it is part
# of the dependency set. Pin both, not one.
FROM python:3.12-slim-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
libcairo2 libgdk-pixbuf-2.0-0 \
fonts-dejavu-core fonts-noto-cjk \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir "weasyprint==62.3"What still hurts after that
The image is small and the memory footprint is a fraction of a browser's, which is the genuine advantage. What you give up is JavaScript: nothing drawn client-side exists by the time the document is rendered, so charts have to be generated server-side as SVG. Fonts must be installed as system packages, and a web font referenced by URL is simply not fetched.
What WeasyPrint is genuinely good at
it implements the parts of the paged media specification that browser engines never did, including running headers and footers in page margin boxes, and it uses a fraction of the memory a browser does. That is worth keeping in view: the problem on this page is where it runs, not what it produces. WeasyPrint is a Python library that implements CSS Paged Media directly, without a browser, on top of the Pango and Cairo text and graphics stack. It needs Pango, Cairo, HarfBuzz, GDK-PixBuf and GLib as system shared libraries. These are not Python packages and no wheel contains them.
The other shape of the answer
Every fix above keeps the renderer inside your deployment, which means the constraint stays yours: the package size, the shared libraries, the fonts, the memory, the patching. The alternative is an HTTP call from Docker to something that already has a browser in it, which turns all of the above into a request and a response. That is what this API is, and it is the reason these pages exist. Whether that trade is right depends on whether rendering is a thing you want to operate.
Frequently asked
Can WeasyPrint run on Docker at all?
Pin both halves. Choose the base image release deliberately, and pin the WeasyPrint version to one whose requirements that release satisfies. Treat the base image tag as part of the dependency specification rather than as a detail. Whether that is worth doing is a separate question from whether it is possible, and the section above on what still hurts is the honest answer to it.
Why does it work locally?
Because a development machine has root, a package manager, several gigabytes of memory, a full font set and no packaging limit. Every one of those is a thing Docker constrains. A renderer that works locally and fails on deploy has not regressed: it has met the first environment that does not give it everything.
Related failures
The same platform with a different renderer, and the same renderer on other platforms.
Puppeteer on Docker: an image with no libraries and no fonts
The launch fails with "error while loading shared libraries: libnss3.so", and once that is fixed every character in the PDF is an empty rectangle.
Playwright on Docker: browsers downloaded outside the image layer
The image builds, and at runtime the browser is missing, or the container downloads it again on every start.
wkhtmltopdf on Docker: the apt package is the wrong build
It installs cleanly, it renders, and the header, the footer and the page numbers are silently missing.
WeasyPrint on AWS Lambda: the libraries pip cannot install
"OSError: cannot load library 'libgobject-2.0-0': libgobject-2.0-0: cannot open shared object file", raised on import rather than on render.
WeasyPrint on Google Cloud Run: a small image that renders differently
The deploy is easy, the memory footprint is a tenth of a browser's, and the documents come out subtly wrong: a chart is missing, a web font fell back, a flex layout collapsed.
WeasyPrint on Azure Functions: GLib is not a pip package
The function app deploys and every invocation fails on import with "cannot load library 'libgobject-2.0-0'".
WeasyPrint on Heroku: an Aptfile and a library path
Deployment succeeds and the app crashes on boot with "cannot load library 'libpango-1.0-0'".
Every renderer, on every platform
The full grid, grouped by what kind of environment the platform is.
What goes wrong when HTML becomes a PDF
Once it deploys, the next set of problems is in the output rather than the infrastructure.
If the render moves off this platform, none of the above is your problem to keep solving. 100 free documents a month, and a playground that needs no signup.