Managed containers / Docker
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.
What is actually wrong
There are two builds of this tool. The one in the distribution repositories is compiled against the system Qt. The one the project publishes is compiled against a patched Qt, and the patches are what implement headers, footers, page numbering and several other options. Both accept the same flags. The unpatched build ignores the ones it cannot honour, without a warning and without a non-zero exit code.
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 wkhtmltopdf
Install the published build rather than the packaged one, and install the fonts and X11 client libraries it links against separately, because the .deb does not pull them in reliably.
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates fontconfig libjpeg62-turbo libxrender1 \
libxext6 xfonts-75dpi xfonts-base fonts-liberation \
&& curl -fsSLO https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bookworm_amd64.deb \
&& dpkg -i wkhtmltox_0.12.6.1-3.bookworm_amd64.deb \
&& rm -f wkhtmltox_*.deb && rm -rf /var/lib/apt/lists/*What still hurts after that
The project is archived and the published builds are pinned to the distributions that existed when it stopped. A newer base image eventually has no matching package, and the choice becomes building Qt from source or staying on an old base. The engine itself is a fork of WebKit from well over a decade ago: flexbox behaves differently, grid does not exist, and a stylesheet written against a current browser will not lay out the way it did in the browser you wrote it in.
What wkhtmltopdf is genuinely good at
it is a single binary with no runtime, so once it is present it does not care what language your application is written in. That is worth keeping in view: the problem on this page is where it runs, not what it produces. wkhtmltopdf is a command line tool built on an old fork of the Qt WebKit engine, invoked as a subprocess with an HTML file and an output path. It needs the binary itself, the X11 and font shared libraries it links against even in headless mode, and a font configuration, because it ships with no fonts of its own.
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 wkhtmltopdf run on Docker at all?
Install the published build rather than the packaged one, and install the fonts and X11 client libraries it links against separately, because the .deb does not pull them in reliably. 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.
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.
wkhtmltopdf on AWS Lambda: a binary with no libraries to link against
"error while loading shared libraries: libXrender.so.1: cannot open shared object file", or the process exits with no output and no error at all.
wkhtmltopdf on Kubernetes: processes that never exit
Over hours or days the pod slows down and eventually stops responding. Process listings show a growing number of wkhtmltopdf processes in a defunct state.
wkhtmltopdf on Heroku: a binary with nowhere to live
Locally it works because the tool is installed on your machine. On a dyno, the command is not found.
wkhtmltopdf on Railway: an apt package the build never runs
Command not found, at runtime, on a build whose logs show no errors at all.
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.