Rendral

Stack · Docker

Generate PDFs in Docker without shipping Chrome

Adding headless Chrome to a container is the step that turns a 120MB image into a 500MB one. You install a hundred-plus shared libraries, discover fonts are missing so CJK text renders as boxes, add --no-sandbox because the default seccomp profile blocks Chrome's, and then repeat the exercise every base-image bump.

There's also the zombie-process problem: Chrome spawns children, and PID 1 in a container doesn't reap them, so you end up adding tini or dumb-init purely to stop defunct processes accumulating.

None of that is inherent to producing a PDF. It's inherent to running a browser. Move the browser out and the Dockerfile goes back to being your app.

01Call it from Docker

render.ts
# Before — Chrome in the image: ~500MB, 100+ libs, fonts, an init shim
# FROM node:20-slim
# RUN apt-get update && apt-get install -y \
#       chromium fonts-liberation fonts-noto-cjk libnss3 libatk1.0-0 \
#       libatk-bridge2.0-0 libcups2 libdrm2 libgbm1 libasound2 tini \
#     && rm -rf /var/lib/apt/lists/*
# ENTRYPOINT ["/usr/bin/tini", "--"]

# After — nothing to install. Rendering happens over HTTPS.
FROM node:20-slim
WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .
ENV NODE_ENV=production
CMD ["node", "server.js"]

Frequently asked

How much smaller is the image?

Typically several hundred megabytes. Chromium plus its shared libraries and a font set is the bulk of a PDF-capable image; removing it usually takes you back to your base image plus your app.

Why do containerised Chrome renders show boxes instead of text?

Missing fonts. Slim base images ship almost none, so CJK and emoji fall back to tofu. Rendering remotely sidesteps the whole font-installation problem.

Do I still need tini or dumb-init?

Not for this. That shim exists to reap the child processes Chrome leaves behind; with no browser in the container there's nothing to reap.

What about --no-sandbox?

It's the usual workaround for Chrome's sandbox conflicting with the container's seccomp profile — and it weakens isolation. Removing the browser removes the tradeoff.

Related

Start rendering in minutes.

A free tier, five endpoints, and SDKs for Node and Python.