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

For a secure Docker setup, keep Chrome’s sandbox enabled, run the container as a dedicated non-root user, and give Chrome only the writable profile and cache paths it needs. When practical, start with Puppeteer’s official Docker image: it includes Chrome for Testing and its dependencies, and its documented runtime setup requires the SYS_ADMIN capability and an init process such as Docker’s --init option. Avoid --no-sandbox as a default: disabling the sandbox removes a major isolation layer.

The examples below show a local PDF workflow, an official-image run command, and a custom-image pattern. The exact capability and filesystem settings depend on your container runtime and deployment policy; validate them in your target environment before relying on them.

Choose the container approach

The official Puppeteer image is the most direct starting point when its base image and runtime requirements fit your deployment. It provides Chrome for Testing, dependencies, and a matching Puppeteer version. For a custom image, you gain control over the base image and installed packages, but you must maintain browser compatibility, shared libraries, executable paths, and permissions yourself.

Approach What it supplies What you must manage
Official Puppeteer image Chrome for Testing, browser dependencies, and a matching Puppeteer version Pin a reviewed image tag; run with the image’s documented sandbox capability; provide writable Chrome paths; reap child processes
Custom image The base image and packages you choose Compatible browser and Puppeteer versions, required shared libraries, Chrome executable path, non-root ownership, sandbox configuration, writable profile/cache paths, and process cleanup

For ordinary operation, preserving the browser sandbox is the security boundary to prioritize. Puppeteer’s troubleshooting guidance says that running without a sandbox is strongly discouraged and recommends configuring one instead. The official Docker guidance says its image is intended to run Chrome sandboxed and requires the SYS_ADMIN capability. That capability is a runtime permission, not a substitute for running as non-root or minimizing mounts and network access.

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

Build a local PDF generator

The application-level flow is simple: launch Chromium, create a page, navigate to the document, call page.pdf(), then close the browser. Here is a runnable Node.js example for a URL supplied as the first command-line argument:

const puppeteer = require('puppeteer');

async function main() {
  const url = process.argv[2];
  if (!url) {
    throw new Error('Usage: node render.js https://example.com');
  }

  const browser = await puppeteer.launch({
    headless: true,
    // Keep Chrome's sandbox enabled. Configure container runtime support
    // rather than adding --no-sandbox here.
    args: [],
    userDataDir: process.env.CHROME_USER_DATA_DIR || '/tmp/chrome-profile',
  });

  try {
    const page = await browser.newPage();
    await page.goto(url, {
      waitUntil: 'networkidle0',
      timeout: 60000,
    });
    await page.pdf({
      path: '/tmp/output.pdf',
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
    });
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

networkidle0 waits for network activity to settle; pages with persistent connections or long-running requests may never reach that condition. In those cases, select a more suitable navigation condition or wait for a meaningful selector instead. The example writes to /tmp/output.pdf, so ensure that directory is writable if you change the output location or run with a read-only root filesystem.

For a PDF that should reflect print styles, leave the page in its default print media mode. Puppeteer’s page.pdf() uses print CSS media by default. If the PDF should look like the screen rendering instead, call await page.emulateMediaType('screen') before page.pdf(). When exact colors matter, use the CSS print-color adjustment property in the page’s styles; printing behavior and the page’s own CSS both affect the result.

Run with the official Puppeteer image

Pin an image tag rather than using an unreviewed floating build. The official image’s documented runtime requirements include SYS_ADMIN for Chrome’s sandbox and --init to handle child-process cleanup. A basic invocation for the example above is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
docker run --rm --init 
  --cap-add=SYS_ADMIN 
  --user pptruser 
  --tmpfs /tmp:rw,nosuid,size=1g 
  -v "$PWD:/home/pptruser/app:ro" 
  -w /home/pptruser/app 
  ghcr.io/puppeteer/puppeteer:<PINNED_VERSION> 
  node render.js https://example.com

Replace <PINNED_VERSION> with the reviewed Puppeteer image version you intend to deploy; do not copy the angle-bracket text literally. Confirm the user name and home directory against the selected image tag before relying on this illustrative command. The application mount is read-only, while /tmp is a writable temporary filesystem for Chrome’s runtime files and the PDF. If the PDF must persist outside the container, mount a separate output directory writable by the container user and write the PDF there.

Container runtimes and host security policies differ. If your environment will not grant the capability required by the official image, do not assume that removing the capability is harmless or that disabling the sandbox is equivalent. Resolve whether the kernel and runtime can support the selected sandbox path, or choose an execution environment that can. If you decide an unsandboxed fallback is unavoidable, treat it as an explicit security exception with reduced renderer isolation, not as a secure configuration.

Build a custom image when you need a different base

A custom image has more moving parts. Install all shared libraries Chrome requires, keep the browser and Puppeteer versions compatible, and set Puppeteer’s executable path when Chrome comes from the base image rather than the package’s expected browser location. Create a dedicated unprivileged runtime account, make the application and browser data paths accessible to that account, and do not run the renderer as root.

This Dockerfile is a structural example, not a complete distribution-specific recipe: the required package names and browser path depend on the base image and the way Chrome is installed. Prefer deriving from Puppeteer’s project Dockerfile when adapting its setup rather than omitting dependencies by guesswork.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
FROM node:<PINNED_BASE_TAG>

# Install the Chrome build and every required shared library using the
# package manager for this base image. Keep Chrome/Puppeteer versions aligned.
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .

ENV XDG_CONFIG_HOME=/tmp/chrome-config 
    XDG_CACHE_HOME=/tmp/chrome-cache 
    CHROME_USER_DATA_DIR=/tmp/chrome-profile

USER node
CMD ["node", "render.js", "https://example.com"]

Before using this pattern, add the base-image-specific browser installation and confirm its executable location. If the installed browser is not at Puppeteer’s configured path, set executablePath in puppeteer.launch() to the actual path. The named node account is only an example; verify that it exists in the chosen base image and owns or can write to the necessary paths.

Set up writable paths and least privilege

Chrome writes configuration, cache, profile, and crash data during startup. A read-only root filesystem or restrictive mounts can therefore prevent launch even when Chrome and its libraries are present. Set XDG_CONFIG_HOME and XDG_CACHE_HOME to writable directories, and set Puppeteer’s userDataDir to a writable directory owned by the runtime user. A temporary filesystem is often suitable for disposable browser state; use a separate writable output mount for PDFs you need to keep.

  • Run as a dedicated non-root user and grant ownership only where the application and browser need it.
  • Keep the root filesystem read-only where practical, while explicitly providing the writable runtime and output locations the job needs.
  • Do not mount host credentials or sensitive files into a renderer that visits untrusted URLs.
  • Restrict outbound network access to what the rendering job requires; a page can initiate requests while it loads.
  • Validate fonts and @font-face resources inside the image. Missing fonts can change line breaks and pagination even if Chrome starts successfully.

Pin, clean up, and operate predictably

Reproducibility depends on the browser, package, and image being treated as a compatible set. Pin the Puppeteer package version and browser or image tag, review updates deliberately, and validate the generated PDFs after upgrading. A floating image tag can change the browser or system dependencies without a corresponding application change.

Use Docker’s --init or an equivalent init process/entrypoint so Chrome child processes are reaped when the container runs. The application should close the browser in a finally block, as in the example, including after navigation or PDF errors. The init process addresses process reaping at container level; it does not replace orderly application 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

No authoritative throughput or memory benchmark is established here, so sizing should be based on the actual pages, fonts, concurrency, and PDF output of your workload. Start with conservative concurrency and observe your own container’s resource use and failure rate. Keep the renderer isolated from secrets and unnecessary network destinations, especially when URLs or page content are supplied by users.

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

Troubleshoot launch and PDF problems

Symptom Likely cause What to check or change
No usable sandbox! The kernel or container runtime cannot provide the sandbox path Chrome is attempting to use, or the required capability is missing. Check that the chosen image and host/runtime support its documented sandbox setup and that the required capability is present. Only consider an unsandboxed exception after evaluating the reduced isolation.
Chrome exits immediately in a read-only container Chrome cannot write its profile, configuration, cache, or related startup data. Set writable XDG_CONFIG_HOME and XDG_CACHE_HOME, choose a writable userDataDir, and make those directories owned or writable by the runtime user.
PDF styles do not match the browser view page.pdf() renders print media by default. Use print CSS intentionally, or call page.emulateMediaType('screen') before PDF generation when screen styles are required. Check print-color adjustment styles if colors differ.
Chrome processes remain or become zombies Child processes are not being reaped, or the app exits without closing the browser. Run with --init or an equivalent process supervisor and close the Puppeteer browser in cleanup logic.
A custom image cannot launch Chrome A shared library is missing, browser and Puppeteer versions are incompatible, executable path is wrong, or files are inaccessible to the non-root user. Check installed browser dependencies, version pairing, the actual Chrome path, and ownership/permissions for the application and profile directories.
Navigation times out or waits indefinitely The site is slow, keeps network activity open, or does not satisfy the selected navigation condition. Inspect the page’s load behavior; adjust timeout or wait condition and, where appropriate, wait for a page-specific selector rather than all network activity to stop.
PDF layout changes between images or deployments Browser, fonts, dependencies, or remote font assets differ. Pin the image and package versions, install and validate required fonts in the image, and check that referenced @font-face assets load in the container.

Or skip the browser setup

If the job is to capture a website rather than run a browser you control inside your own container, ScreenshotNeo offers a screenshot API that can return PNG, JPEG, WebP, or PDF. It is a hosted alternative, not local Puppeteer: the code below requests a WebP capture. See the ScreenshotNeo API documentation for its request options, including PDF capture.

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

ScreenshotNeo accepts cookie/consent banners 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, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its 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 per month with no card; paid plans start at $5 for 3,000 shots.

Sign up for 1,000 free screenshots a month, with no card required.

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.

Frequently Asked Questions

Can I use Puppeteer to generate PDFs without internet access?

Puppeteer can render local content, but the container still needs the installed browser, libraries, and any fonts or assets the document references. The example here navigates to a URL; a local-file workflow must supply the file and its dependent assets inside the container.

Does page.pdf() automatically include background colors?

The example sets printBackground: true. The page’s CSS and print-media rules still determine which content and colors are available to print.

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.