DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
Chrome sandbox

How to Securely Run Puppeteer Chrome for Local PDF Generation in Docker

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Chrome’s sandbox, run the browser as a non-root user, and give it only the writable directories and capability it needs. For the least maintenance, start with the versioned official Puppeteer image, run it with Docker’s --init flag and the image’s required SYS_ADMIN capability, and keep --no-sandbox out of normal operation. The example below generates a PDF entirely inside Docker, preserves print-CSS behavior intentionally, and includes the fixes for the most common container failures.

What a secure Docker setup looks like

Chrome’s sandbox is the primary isolation boundary between a web page and the browser process. Disabling it removes a major layer of protection, so Puppeteer’s own guidance strongly discourages running without a sandbox. A secure baseline has these properties:

  • A version-pinned Puppeteer package and a browser/image tag reviewed together.
  • A non-root runtime account with ownership only of the application and temporary Chrome profile directories.
  • The official Puppeteer image when practical, because it supplies Chrome for Testing and its shared-library dependencies as a matching baseline.
  • The capability required by that image for sandboxed Chrome: SYS_ADMIN.
  • --init (or an equivalent init process) so Chrome’s child processes are reaped.
  • Writable locations for Chrome configuration, cache, profile, and crash data, even when the container root filesystem is read-only.
  • Restricted outbound networking and no ambient host credentials when rendering untrusted URLs.

The official image is designed for sandbox mode and therefore requires SYS_ADMIN at runtime. Treat that capability as part of the image contract, not as a reason to add --no-sandbox.

Choose the image and pin the versions

Official image (the practical default)

Use a specific ghcr.io/puppeteer/puppeteer:<version> tag that your team has reviewed. Keep the Puppeteer dependency in package-lock.json (or another lockfile) and update the image and package together. Do not build production containers from an unreviewed floating tag.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Custom image (when you need a smaller or specialized base)

A custom image must provide every shared library Chrome needs, a compatible browser and Puppeteer version, and a known executable path. Create a dedicated non-root user, make that user the owner of the application and browser-profile directories, and set executablePath if Chrome is supplied by the base image rather than Puppeteer. Missing one of these details commonly causes an immediate launch failure.

Build a local PDF renderer

The following example assumes a reviewed official image tag. Replace the tag with the exact version you have approved, then commit the resulting Dockerfile and lockfile together.

Project files

pdf-renderer/
  Dockerfile
  package.json
  package-lock.json
  render.js

package.json

{
  "name": "docker-puppeteer-pdf",
  "private": true,
  "type": "module",
  "scripts": { "render": "node render.js" },
  "dependencies": { "puppeteer": "24.10.2" }
}

The version shown is an example pin; choose a browser/image combination that you have tested and record it in your lockfile. The important property is that the versions are deliberate and reproducible, not that every deployment uses the same number.

Dockerfile

FROM ghcr.io/puppeteer/puppeteer:24.10.2

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY render.js ./

# Chrome writes these locations during startup. They are supplied as writable
# tmpfs mounts at runtime, even when the container root is read-only.
ENV XDG_CONFIG_HOME=/tmp/chrome-config 
    XDG_CACHE_HOME=/tmp/chrome-cache 
    HOME=/tmp

CMD ["node", "render.js"]

For a custom base image, add the browser’s shared libraries, create an application user, and grant that user ownership of /app and the profile directory. Do not silently fall back to root just because Chrome needs a writable home directory.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

render.js

import puppeteer from 'puppeteer';

const target = process.env.TARGET_URL || 'https://example.com';
const output = process.env.OUTPUT || '/tmp/output.pdf';

const browser = await puppeteer.launch({
  headless: true,
  // No --no-sandbox: the container is configured for Chrome's sandbox.
  userDataDir: '/tmp/chrome-profile',
  args: [
    '--disable-dev-shm-usage'
  ]
});

try {
  const page = await browser.newPage();
  await page.goto(target, {
    waitUntil: 'networkidle2',
    timeout: 60000
  });

  // page.pdf() uses print media by default. Remove this line when print CSS
  // is desired; keep it when the PDF should match the screen stylesheet.
  await page.emulateMediaType('screen');

  await page.pdf({
    path: output,
    format: 'A4',
    printBackground: true,
    margin: { top: '16mm', right: '16mm', bottom: '16mm', left: '16mm' }
  });

  console.log(`Wrote ${output}`);
} finally {
  await browser.close();
}

--disable-dev-shm-usage makes Chrome use the writable filesystem instead of a small default shared-memory mount. It can be useful in constrained containers, although a deliberately sized /dev/shm mount is another valid approach.

Build and run it with least privilege

  1. Run npm install once in the project to create and review package-lock.json.
  2. Build the image: docker build -t local-puppeteer-pdf:reviewed ..
  3. Run with an init process, the sandbox capability, and writable temporary directories:
docker run --rm 
  --init 
  --cap-add=SYS_ADMIN 
  --read-only 
  --tmpfs /tmp:rw,nosuid,size=512m 
  -e TARGET_URL=https://example.com 
  -e OUTPUT=/tmp/output.pdf 
  local-puppeteer-pdf:reviewed

The PDF is written inside the container’s temporary filesystem and disappears with --rm. For a persistent artifact, bind-mount a destination directory owned by the runtime UID and set OUTPUT to that path. Mount only that directory; never mount the host’s home directory, Docker socket, cloud credentials, SSH keys, or browser profile.

Verify the runtime identity and sandbox

Before processing real documents, inspect the image’s default identity with docker run --rm --entrypoint id local-puppeteer-pdf:reviewed. It should not be root. If you build a custom image, create a fixed UID/GID, set USER to that account, and test that it can write only to /app and the temporary Chrome paths.

Run a harmless test URL first. A successful run should print the output path and leave a readable PDF. If Chrome reports that no usable sandbox exists, stop and fix the container capability or kernel support before considering any unsandboxed fallback.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make PDF output predictable

Print media versus screen media

page.pdf() selects print CSS by default. That is usually correct for invoices and formal documents, but it can hide navigation, change colors, or select print-specific layouts. Calling page.emulateMediaType('screen') before page.pdf() makes the PDF follow screen styles instead. For color-sensitive output, use CSS print-color adjustment in the page itself and enable printBackground as shown above.

Fonts and web assets

Fonts installed on a developer laptop are not automatically present in the image. Package the required fonts or install them in the image, and ensure @font-face URLs are reachable from the container. Missing fonts alter line wrapping and pagination even when Chrome launches normally. If a page loads data after navigation, wait for a meaningful selector or application-ready signal rather than relying only on a generic network-idle event.

Read-only filesystems and profiles

Chrome writes configuration, cache, crash reports, and profile data during startup. Set XDG_CONFIG_HOME and XDG_CACHE_HOME to writable directories and point userDataDir at a writable directory owned by the runtime user. A read-only root filesystem is compatible with Chrome only when these write paths are explicitly provided.

Isolation when rendering untrusted URLs

A sandboxed renderer is necessary but not sufficient for a hostile page. Place the worker in a separate network policy or isolated namespace where possible. Allow only the outbound destinations it needs, avoid service-account tokens in environment variables, and do not expose cloud metadata endpoints or host-mounted secrets. Use a fresh temporary profile per job when pages may contain sensitive state, and discard it after the browser closes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Limit navigation timeouts, output size, and concurrency at the job-queue layer. There is no authoritative throughput or memory benchmark in the available documentation, so size these limits from measurements in your own pages rather than from a borrowed number.

Common failures and precise fixes

“No usable sandbox!”

Cause: Chrome cannot access a supported sandbox path, or the container was started without the capability required by the image.

Fix: Confirm kernel support, use the documented official-image runtime settings, and include --cap-add=SYS_ADMIN. Verify that you are not accidentally running a custom image with incompatible permissions. Only when a sandbox genuinely cannot be configured should you evaluate an unsandboxed fallback, and document that it removes a major isolation layer.

Chrome exits immediately in a read-only container

Cause: The profile, cache, configuration, or crash directory is not writable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: Provide writable XDG_CONFIG_HOME, XDG_CACHE_HOME, HOME, and userDataDir paths. A tmpfs mounted at /tmp is suitable for short-lived jobs; a persistent profile is usually unnecessary.

PDF styling differs from the page

Cause: PDF generation uses print media by default, or fonts and assets differ inside the image.

Fix: Choose print or screen media explicitly, set printBackground when needed, and install or bundle the fonts used by the page. Capture a diagnostic screenshot or inspect computed styles inside the same container to distinguish CSS from missing assets.

Zombie Chrome processes accumulate

Cause: The container has no init process to reap orphaned children, or application errors skip cleanup.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Docker Container Linux Devops Programming Coding T-Shirt
  • Docker, Docker Swarm, Docker Compose, Programmer, Developer, Coding, Programming, Software Engineer, Code, DevOps, Deploy, Deployment, Kubernetes, Salt, Puppet, Chef, Terraform, Container, AWS, Azure, Cloud, Geek, Funny, Computer, Software, Tech, IT
  • Integration, Scrum, Compile, Compilation, Science, Bug, Debug, Python, Linux, Java, Javascript, Scala, Dotnet, Kotlin
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem

Fix: Start Docker with --init, keep browser shutdown in a finally block, and remove the container after each job when practical. An equivalent process supervisor is acceptable.

A custom image cannot launch Chrome

Cause: Missing shared libraries, a browser/Puppeteer mismatch, an incorrect executable path, or non-root ownership problems.

Fix: Compare the image’s installed libraries with the browser’s requirements, pin compatible versions, set executablePath when Chrome is not in Puppeteer’s expected location, and test as the actual runtime UID rather than as root.

Navigation hangs or produces incomplete PDFs

Cause: The page depends on long-lived connections, delayed JavaScript, blocked third-party resources, or a URL that is inaccessible from the container network.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: Use an explicit timeout, wait for a page-specific selector or readiness condition, log the final URL and HTTP failures, and permit only the domains required by the document. Do not increase timeouts indefinitely; a failed job should be observable and bounded.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Operational checklist

  • Pin both the Puppeteer package and browser/image tag.
  • Run as a dedicated non-root user.
  • Keep Chrome sandboxing enabled and supply the image’s required capability.
  • Start the container with --init.
  • Provide writable, disposable profile and cache paths.
  • Validate fonts, @font-face resources, and print/screen CSS in the image.
  • Restrict network egress and never mount host credentials for untrusted pages.
  • Close the browser in all code paths and delete temporary profiles after jobs.
  • Measure your own page classes for memory, duration, and safe concurrency; no official benchmark establishes universal limits.

Or skip the browser setup

If your requirement is simply a clean screenshot or PDF of a URL, ScreenshotNeo provides a hosted API and MCP server instead of making you maintain Chrome images and sandbox capabilities. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the page verdict and billing status in headers.

One GET request returns PNG, JPEG, WebP, or PDF output. The same service supports full-page capture with lazy images, CSS-selector elements, dark mode, device presets, custom viewports and retina scale, PDF paper sizes and page ranges, custom CSS and JavaScript, pre-capture clicks, selector waits, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

See the ScreenshotNeo documentation for authentication and option names. The following calls use the supplied API shape:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to try the 1,000 monthly screenshots without entering a card.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.