Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
If table headers overlap rows in PDFs generated inside Docker, first reproduce the failure with the exact Chromium binary in the production image. Puppeteer’s page.pdf() prints using the print CSS media type, and the container’s Chromium build, fonts, and print geometry can all affect pagination. A CSS rule such as thead { display: table-header-group; } is a sensible baseline, not a guaranteed fix: Chromium can ignore repeated table headers in PDF output, and complex rows or rowspans can break across pages in unexpected ways.
Start by locating the failure
Do not begin by changing production CSS based on a PDF that looks right in your desktop browser. Determine whether the problem comes from your HTML and print styles, Puppeteer’s PDF options, or the Chromium and font environment inside the container. Matching Puppeteer package versions does not prove that local and production renders use equivalent Chromium builds.
- Record the render environment. Write down the Puppeteer and Node.js versions, operating system, Docker base image and image digest, Chromium version and executable path, installed fonts, and every PDF option passed to
page.pdf(). - Make a minimal fixture. Save a small HTML file containing the failing table and its print CSS. Keep application JavaScript out of the fixture except for inserting the table data needed to reproduce the problem.
- Print with the container’s Chromium directly. From inside the production image, use the same Chromium executable that Puppeteer launches:
chromium --headless --disable-gpu --no-sandbox --print-to-pdf=/tmp/table-cli.pdf file:///tmp/table-fixture.html. The executable name varies by image; use its actual path or name. Compare this PDF with one generated from the same fixture by Puppeteer. - Change one variable at a time. Compare the failing container with a known-good environment, then check the Chromium build, font files, print CSS, paper size, margins, scale, and other PDF options. Keep the fixture and input data constant.
- Keep a regression fixture. Render the same HTML inside the pinned CI image and check both page count and visual output when changing Chromium, fonts, CSS, or report code.
If command-line Chromium and Puppeteer both produce the defect inside the image, focus on the container’s browser and print pipeline before changing application code. A reported reproduction with Alpine Linux Chromium 123.0.6312.122 also appeared when printing with Chromium’s command-line interface. That is evidence that the container renderer can be responsible, not proof that every Alpine image or Docker deployment has the same issue.
Why Docker can change table pagination
Puppeteer’s page.pdf() generates the PDF with the print CSS media type. That means @media print rules apply, and the printable page rectangle—not the browser window you inspected on screen—determines where content breaks. The Puppeteer API documentation describes this as generating a PDF with the print CSS media type.
#1 Best Overall
A container can use a different Chromium build, font set, or operating-system rendering environment from a developer workstation. Any of those differences may change text width, line wrapping, row height, or pagination. A small change in wrapping can move a row onto another page, where repeated headers, borders, or row styling become visibly incorrect. Also check whether a site or application stylesheet changes between screen and print media.
There are known Chromium/Puppeteer PDF failure modes for tables. Puppeteer issue #10020 documents a reproducible case in which display: table-header-group is ignored in PDF output. Issue #6388 documents uneven borders and shifted styling around page breaks, particularly with rowspans. These issue reports establish that such failure modes exist; they do not establish how often they occur or guarantee that a particular workaround will fix a particular report.
Use semantic table markup and scoped print CSS
Use one actual HTML table with a single header group and a body group. Do not imitate a repeating header with absolutely positioned elements: those elements can be painted independently of table pagination, so their position may not track the rows that move between pages.
@media print {
table { width: 100%; border-collapse: collapse; }
thead { display: table-header-group; }
tbody { display: table-row-group; }
tr { break-inside: avoid; page-break-inside: avoid; }
th, td { break-inside: avoid; }
}
Keep the selectors scoped to the relevant table if the document contains other tables. The rules express the intended print layout: treat the header and body as table groups, and ask the browser not to break rows or cells internally. They are mitigations, not a guarantee that Chromium will repeat a header correctly or keep every row intact.
In particular, break-inside: avoid cannot make a row fit when the row itself is taller than the remaining printable area. A row may still split, move, or produce distorted borders around a page break. If a row is too tall for a page, reduce its content or divide it into smaller logical units rather than expecting a break-avoidance rule to override page geometry.
Make PDF geometry and font loading explicit
Set the page dimensions and rendering options deliberately so a local run and a container run have the same intended printable area. Puppeteer’s PDF options include format, width, height, margin, scale, preferCSSPageSize, printBackground, displayHeaderFooter, and waitForFonts. Do not set both a paper format and custom width and height unless you have checked how your chosen options interact; choose a clear page-size strategy and keep it consistent.
For example, when the report’s CSS defines its own paper size, a render can make that choice explicit:
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsconst pdf = await page.pdf({
preferCSSPageSize: true,
margin: { top: '12mm', right: '12mm', bottom: '12mm', left: '12mm' },
scale: 1,
printBackground: true,
displayHeaderFooter: false,
waitForFonts: true,
});
Alternatively, specify a fixed format or a fixed width and height rather than relying on defaults. Make the chosen paper size, margins, and scale identical in your known-good and Docker runs. A smaller printable area can push a row to the next page; changed scale or font metrics can alter wrapping and cause the same effect.
Puppeteer’s guide states that fonts are waited for by default, but that does not make font files interchangeable across images. Install and pin the fonts your report needs in the container, and ensure the page’s own font-loading work has completed before printing. If the container substitutes a font with different metrics, headings and cells can wrap differently even when the CSS and HTML are unchanged.
Keep displayHeaderFooter off unless the PDF needs Puppeteer’s page-level header or footer templates. Those templates are separate from a table’s <thead>; if you enable them, check their margins and available page area as part of the same geometry comparison.
Reduce table structures that are fragile across pages
Cross-page rowspans are a common source of visual trouble. A cell spanning rows that land on different pages complicates pagination and can leave borders or vertical alignment looking wrong. Prefer data that does not require a rowspan across a page boundary. If the visual grouping matters, repeat the group label in each row or restructure the content into smaller tables.
Recommended Free Tools
When an individual table row or group cannot be laid out reliably across pages, divide the data into page-sized table sections and include an explicit header row in each section. This gives you control over where a new table begins and which header accompanies its rows, rather than relying on automatic repeated-header behavior for a problematic structure. Make each section small enough that its header and rows can fit within the printable area; an oversized row still needs to be split or redesigned.
Use this fallback when a report’s layout must be deterministic and ordinary semantic-table pagination remains inconsistent. It trades automatic pagination for more control over page boundaries, and may require application logic to group or chunk data. The right chunk size depends on the content and selected page geometry; there is no universal row count that will fit every page.
Minimal Puppeteer reproduction
Reproduce the defect with the HTML file inside the same image and browser installation used in production. This example deliberately specifies PDF geometry and waits for the page to load; adapt the executable path, fixture path, and page-size strategy to your deployment.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-gpu'],
});
try {
const page = await browser.newPage();
await page.goto('file:///tmp/table-fixture.html', {
waitUntil: 'networkidle0',
});
await page.evaluate(() => document.fonts.ready);
const pdf = await page.pdf({
format: 'A4',
margin: {
top: '12mm',
right: '12mm',
bottom: '12mm',
left: '12mm',
},
scale: 1,
preferCSSPageSize: false,
printBackground: true,
displayHeaderFooter: false,
waitForFonts: true,
});
require('fs').writeFileSync('/tmp/table-puppeteer.pdf', pdf);
} finally {
await browser.close();
}
})();
This is a diagnostic fixture, not a universal production configuration. If your document depends on network resources, use a deliberate readiness condition appropriate to that page rather than assuming network idle proves every application task is finished. If the report’s CSS sets page size, test a matching preferCSSPageSize configuration instead of overriding it with a different paper size.
Free tools Windows power users keep installed
One-click scans. No signup required.
Troubleshoot by symptom
| Symptom | Likely cause to investigate | Next check |
|---|---|---|
| Headers overlap rows in both Puppeteer and direct Chromium output in Docker | The container Chromium build, installed fonts, print CSS, or geometry may be responsible. | Compare the exact browser binary and image digest with the known-good environment; then vary fonts and PDF options one at a time. |
| Direct Chromium output is correct but Puppeteer output is not | The launch environment or options may differ between the two paths. | Compare the executable Puppeteer launches, command-line arguments, media behavior, and every PDF option. |
| Header text or rows wrap differently only in Docker | Different or missing font files can change text metrics and row heights. | Install the expected fonts in the image, wait for font readiness, and rerender the same fixture. |
| A header is missing or drawn over body content after a page break | Repeated table-header handling may be failing in Chromium PDF output. | Verify semantic table markup and the print-only header-group rule; if the minimal fixture still fails, use explicit page-sized table chunks. |
| Borders or row styling shift around a page boundary | A split row or rowspan crossing the boundary may be confusing table pagination. | Remove cross-page rowspans where possible and restructure the data into smaller tables or page chunks. |
A row still breaks despite break-inside: avoid |
The row may not fit in the remaining printable area, or the renderer may not honor the requested avoidance in that case. | Reduce or divide the row, and check the available page area after margins, scale, and headers. |
| The defect appears after changing margins, paper size, or scale | The printable rectangle or line wrapping changed, moving the break point. | Restore explicit, matching geometry and verify whether the report’s CSS page size should take precedence. |
Performance, reliability, and operational choices
A small HTML fixture and direct Chromium run are usually the fastest way to separate a browser-level defect from application complexity. Keep the fixture around: it makes upgrades to the base image, Chromium, fonts, and Puppeteer easier to evaluate against a stable case. A page-count check can catch pagination changes, while a visual comparison is needed to catch overlap, shifted borders, and other appearance defects.
For teams that need exact output, explicitly chunking a difficult table makes page layout more predictable but adds report-generation logic. A managed browser-class HTML-to-PDF renderer is another operational option for teams that do not want to maintain Chromium themselves. Evaluate that trade-off against your requirements; a different renderer is not automatically a fix, and availability and terms need to be checked with the provider.
Or skip the browser setup
If the job is to capture a publicly reachable web page as an image or PDF rather than to control a complex report layout inside your own Puppeteer application, ScreenshotNeo offers a one-request screenshot API. It does not replace diagnosing a custom Puppeteer table when you need control over your own renderer and pagination. For a URL capture, one cURL request is:
Rank #4
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Its clean-shot flow accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchSign up free for 1,000 screenshots a month, with no card required.
Build a regression test around the actual container
Once the fixture renders as intended, run it in CI with the pinned image rather than relying only on a developer workstation. Save the relevant input HTML and data, record the PDF options alongside the expected output, and inspect both page count and rendered pages when a test changes. A page-count check alone cannot detect every overlap; visual review or image comparison is needed for layout defects.
When an upgrade changes the PDF, compare the old and new browser versions and image digests first, then fonts and geometry. That gives you a tractable path to identify a renderer change without mistaking it for a regression in table data or application logic.
Frequently Asked Questions
Does this issue prove that Docker itself is incompatible with Puppeteer PDF tables?
No. Docker can expose differences in Chromium builds, fonts, and print behavior, but the reports establish specific failure modes rather than a general incompatibility or an incidence rate.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Should I use screen media when generating the PDF to avoid print pagination problems?
Not as a general fix. page.pdf() uses print media; switch to screen media only when you intentionally want to test screen styles, not as a substitute for diagnosing the print layout.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

