Remove 400 MB of Chromium from your Docker image

Migrate from Puppeteer to Papyr

Puppeteer PDF generation works fine locally. Then you deploy to Linux and get blank PDFs, missing fonts, and a Dockerfile that takes 10 minutes to build. Papyr is one HTTP call. Your HTML goes in, a PDF comes back, and Chromium is off your server.

Why Puppeteer is painful for PDFs

Blank PDFs on Linux

Puppeteer launches a real Chromium instance. On Alpine Linux (the default Docker base), it's missing dozens of system libraries. The result is a blank PDF or a crash — and the error message tells you nothing useful.

400 MB Docker images

A minimal Node app with Puppeteer weighs ~450 MB. Add Chromium system deps to Alpine and you're at 600+ MB. Slow builds, slow cold starts, bloated image registry.

Memory leaks and zombie processes

If a page crashes or your code throws before browser.close(), the Chromium process keeps running. A few leaked browsers and your server runs out of memory. You end up writing retry logic around something that shouldn't need it.

Font rendering inconsistency

Fonts that look right in dev look wrong in production. System font stacks differ between macOS and Linux. Google Fonts require network access at render time. Certificates need to be trusted in the container.

waitUntil guesswork

networkidle0 waits for all network requests to settle — but it waits up to 30 seconds if anything keeps polling. networkidle2 cuts off early. You end up with flaky PDFs depending on which external resources your HTML loads.

Migration guide

Replace your Puppeteer function with one that calls Papyr. The signature is the same — HTML in, Buffer out.

Before (Puppeteer)

Before — Puppeteer
const puppeteer = require("puppeteer");

async function htmlToPdf(html) {
  const browser = await puppeteer.launch({
    args: ["--no-sandbox", "--disable-setuid-sandbox"],
  });
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: "networkidle0" });
  const pdf = await page.pdf({ format: "A4" });
  await browser.close();
  return pdf;
}

After (Papyr) — Node.js

After — Papyr
async function htmlToPdf(html) {
  const res = await fetch("https://api.getpapyr.dev/v1/render/pdf", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.PAPYR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html, options: { format: "A4" } }),
  });
  if (!res.ok) throw new Error(`PDF generation failed: ${res.status}`);
  return Buffer.from(await res.arrayBuffer());
}

Same function signature. Callers need no changes.

After (Papyr) — Python

Python
import os
import requests

def html_to_pdf(html: str) -> bytes:
    res = requests.post(
        "https://api.getpapyr.dev/v1/render/pdf",
        headers={"Authorization": f"Bearer {os.environ['PAPYR_API_KEY']}"},
        json={"html": html, "options": {"format": "A4"}},
    )
    res.raise_for_status()
    return res.content

Dockerfile before and after

Before — ~400 MB extra
# Before: Chromium + system deps = ~400 MB extra
FROM node:20-alpine
RUN apk add --no-cache \
    chromium nss freetype harfbuzz ca-certificates ttf-freefont
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
After — nothing extra
# After: nothing extra needed
FROM node:20-alpine
# No Chromium. No system fonts. No workarounds.

Options mapping

Puppeteer page.pdf() optionPapyr option
format: "A4"options.format: "A4"
landscape: trueoptions.landscape: true
margin: { top: "20mm" }options.margin.top: "20mm"
printBackground: trueoptions.printBackground: true
width: '210mm'options.format: "A4" (use named sizes)

What you gain

No Chromium in your Docker image — smaller images, faster builds, faster cold starts
No --no-sandbox flags, no setuid workarounds, no Linux system library hunting
No memory leaks from unclosed browser instances
Consistent output across every environment — dev, CI, staging, production
Google Fonts and @font-face work without network access from your server
No waitUntil guesswork — Papyr waits for the page to be fully rendered

Ready to remove Puppeteer?

Free tier includes 100 documents/month. No credit card required.