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

To take website screenshots with Puppeteer in Docker, install puppeteer in your Node.js project, let its install step download a compatible Chrome for Testing, and build an image that includes Chrome’s Linux libraries and writable profile and output paths. Run the browser as a non-root user, then use page.screenshot() to save the result. This guide covers a custom image you can adapt, the official Puppeteer image, screenshot options, and fixes for common container failures.

Choose how Puppeteer will get its browser

The package choice determines who manages Chrome. For a conventional app image, use puppeteer: by default its install process downloads a compatible Chrome for Testing. Starting with Puppeteer v21.6.0, that process also downloads chrome-headless-shell. The browser is normally stored under $HOME/.cache/puppeteer, the documented default since v19.0.0. The exact browser version follows the installed Puppeteer version, so preserve a lockfile and build the package and browser together.

Use puppeteer-core instead when you deliberately supply the browser yourself or connect to a remote browser. It does not download Chrome. For a local browser, configure its executablePath or channel when launching; for a remote browser, configure the connection appropriate to that service.

Installation scripts matter: modern npm, pnpm, Yarn Berry, Bun, and Deno setups may block dependency install scripts. If that happens, the Puppeteer package can be present while its browser is missing. Allow the Puppeteer install script during the image build or explicitly run npx puppeteer browsers install.

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

Puppeteer’s installation documentation estimates Chrome for Testing downloads at approximately 282 MB for Linux, 170 MB for macOS, and 280 MB for Windows. These are browser-download estimates, not a guarantee of final Docker image size: your base image, system libraries, fonts, application files, and image layers add to it. See the Puppeteer installation guide for current installation behavior.

Build a custom Docker image

A custom image gives you control over the Node base, system packages, fonts, user, and output layout. The example below uses Debian Bookworm and installs Chrome dependencies with Puppeteer’s browser installer. The project’s official Dockerfile is a useful model for dependency and permission choices, but its pinned base and package list are project-specific and can change. Consult the current Puppeteer Dockerfile when adapting this example to your version and distribution.

Create package.json with Puppeteer as an application dependency:

{
  "name": "docker-screenshot",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "screenshot": "node screenshot.js"
  },
  "dependencies": {
    "puppeteer": "^25.8.0"
  }
}

The version shown is an example package range, not a claim that it will remain current. Pin the version and commit the generated lockfile for repeatable builds. Create screenshot.js:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import puppeteer from 'puppeteer';

const url = process.env.TARGET_URL ?? 'https://example.com';
const output = process.env.OUTPUT_PATH ?? '/output/page.png';

const browser = await puppeteer.launch({
  headless: true,
});

try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1365, height: 900, deviceScaleFactor: 1 });
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 60_000 });
  await page.screenshot({ path: output, fullPage: true });
  console.log(`Saved ${output}`);
} finally {
  await browser.close();
}

networkidle2 is one possible readiness choice, not a universal setting. Pages with analytics, streaming requests, client-side rendering, or delayed content may never become idle when expected. Choose navigation waits and explicit page-readiness checks to match the target site; do not assume one wait condition guarantees that every dynamic element has rendered.

Create a Dockerfile:

FROM node:24-bookworm-slim

ENV PUPPETEER_CACHE_DIR=/home/pptruser/.cache/puppeteer
WORKDIR /app

# Install application dependencies first for better layer reuse.
COPY package*.json ./
RUN npm ci

# Install the browser selected for this Puppeteer version and its OS libraries.
RUN npx puppeteer browsers install chrome --install-deps

COPY screenshot.js ./
RUN mkdir -p /output 
    && useradd --create-home --shell /usr/sbin/nologin pptruser 
    && mkdir -p /home/pptruser/.cache/puppeteer 
    && chown -R pptruser:pptruser /app /output /home/pptruser

USER pptruser
CMD ["npm", "run", "screenshot"]

Use a Debian-based Node image for this pattern. The browser installer’s --install-deps option uses the base distribution’s package manager to install required system dependencies; it needs suitable privileges during the build, which this Dockerfile has before switching to pptruser. If your Puppeteer version or base distribution does not support that option, use the current official Dockerfile and the relevant distribution’s dependency list instead. If you change the browser installation approach, verify that the installed browser and Puppeteer versions are compatible.

Build and run it, mounting a host directory so the output survives container exit:

mkdir -p output
docker build -t puppeteer-shot .
docker run --init --rm 
  -e TARGET_URL=https://example.com 
  -e OUTPUT_PATH=/output/page.png 
  -v "$PWD/output:/output" 
  puppeteer-shot

After a successful run, look for output/page.png on the host. Docker’s --init option provides an init process that can reap child processes; Puppeteer’s Docker troubleshooting guidance recommends it where available. The mounted output directory must be writable by the container user. On hosts where the directory’s ownership or permissions prevent writes, adjust the host directory permissions or use a container user and volume ownership appropriate to your runtime.

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.

Install-script alternative

If your package manager blocks Puppeteer’s install script, explicitly install the browser during the build after installing dependencies:

RUN npx puppeteer browsers install chrome --install-deps

Alternatively, configure your package manager to allow Puppeteer’s install script. Do not do both without a reason: the goal is to ensure one compatible browser is installed in the image. Check the build output for a browser download and dependency-install result.

Use the official Puppeteer container instead

Puppeteer also publishes a container image through GitHub Container Registry. It can be quicker than maintaining a custom dependency list. At the time the project registry was checked for this guide, it showed version 25.8.0; image tags are volatile, so check the official container registry and select an available tag deliberately rather than assuming that observed tag remains current. The project’s Docker directory also provides implementation details.

Choose the official image when its Node, browser, libraries, and user setup fit your application. Choose a custom image when you need a different base, additional system packages or fonts, a particular app layout, or a controlled update policy. In either case, test a build that actually launches a page and writes the expected output; an image existing in the registry does not prove that its defaults suit your workload.

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

Set screenshot size, format, and output intentionally

page.screenshot() returns image bytes (a Uint8Array) unless you request base64 output. Supplying path writes the image; a relative path resolves from the process working directory. Without a path, the method does not save a file to disk. In Docker, a saved file is inside the container unless you return it from your application or persist it through a mounted volume.

Option Effect Use it when
path Writes the image at the specified path; extension may determine format. You need a file. Ensure its directory exists and is writable, and mount it if it must persist outside the container.
type Selects PNG, JPEG, or WebP. PNG is the default; the type can be inferred from the path extension. You need an explicit format rather than relying on the filename.
fullPage Captures the full page rather than only the viewport; default is false. You need a page-length image. Long pages can produce large output files.
clip Captures a specified rectangular region. You need a defined page area rather than the viewport or full document.
omitBackground Omits the default white background, allowing transparency. You need a transparent image background.
quality Sets quality from 0 to 100 for formats where it applies; it does not apply to PNG. You need to tune a supported lossy format’s file size and visual quality.

For full-page output, lazy-loaded images may not appear if the page has not had a chance to load them. Scroll the page or use a page-specific readiness procedure before capturing when those images matter. Fonts are another frequent source of differences: include fonts needed for the languages and styles you render, and wait for the page’s fonts and layout to settle before taking a screenshot.

Handle container paths, permissions, and browser security

Keep Chrome non-root where possible

Run the browser as an unprivileged user, as the example does. Puppeteer’s Docker troubleshooting example uses a non-privileged pptruser and avoids requiring --no-sandbox in that setup. Do not make --no-sandbox the default fix for a launch error: it changes Chrome’s security posture, and the right configuration depends on the container runtime. Review the Puppeteer troubleshooting guide and your deployment’s isolation requirements before changing sandbox settings.

Provide writable profile and cache locations

Chrome writes profile, configuration, and cache files. A container with a read-only root filesystem can fail even when the screenshot destination is writable. Puppeteer documents setting XDG_CONFIG_HOME and XDG_CACHE_HOME to writable /tmp paths and setting launch’s userDataDir to a writable profile directory, or mounting writable volumes owned by the runtime user. For example:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
ENV XDG_CONFIG_HOME=/tmp/.chromium-config 
    XDG_CACHE_HOME=/tmp/.chromium-cache

Then configure a writable profile in the launch options if needed:

const browser = await puppeteer.launch({
  headless: true,
  userDataDir: '/tmp/puppeteer-profile',
});

For a long-running service, consider a dedicated writable temporary directory or volume and make its lifecycle explicit. Do not assume that a browser cache, profile, or screenshot saved in a container’s writable layer will remain after the container is removed.

Be cautious with Alpine

Puppeteer’s documentation states that Chrome does not support Alpine out of the box. If Alpine is a requirement, verify the exact Chromium and Puppeteer combination and the needed compatibility libraries rather than copying a Debian package list or assuming the standard Chrome download will work. The simpler path for the example in this article is a Debian-based image.

Troubleshoot failed builds and screenshots

Symptom Likely cause What to check or change
“Could not find Chrome” or browser missing at launch A package-manager policy blocked Puppeteer’s install script, or the browser was not installed in the image. Inspect build logs and the Puppeteer cache location. Allow the install script or run npx puppeteer browsers install during the build; rebuild the image rather than relying on a browser on the host.
Chrome exits immediately or reports a missing shared library The base image lacks an OS library required by the selected Chrome build. Install dependencies for that distribution and browser version. Use the current official Dockerfile or the distribution package guidance as a starting point; old package lists may no longer match.
Browser launch fails under a restricted runtime The user, sandbox, or container security configuration is incompatible. Confirm the process is not unintentionally running as root and review the runtime’s security settings and Puppeteer’s troubleshooting guidance. Avoid reflexively adding --no-sandbox.
Screenshot file is absent on the host The file was written only to the container filesystem, or the output path was not writable. Check the path printed by the program, create the directory, verify container-user permissions, and bind-mount the output directory or return the image bytes through your application.
Failure occurs with a read-only filesystem Chrome cannot create profile, configuration, or cache files. Set writable XDG_CONFIG_HOME and XDG_CACHE_HOME, use a writable userDataDir, or mount an owned writable volume.
Navigation times out or screenshot is incomplete The page is slow, keeps network connections open, or renders content after navigation. Choose a timeout and wait condition for the site. For late content, wait for a selector or another application-specific readiness signal before capture rather than assuming network idle is appropriate.
Some images or glyphs are missing Lazy-loaded content was never requested, or the image lacks a needed font. Scroll or otherwise trigger lazy loading before capture, wait for layout and fonts, and add required fonts to the image.
Many browser child processes linger Container process handling is not reaping children as expected. Close the browser in a finally block and run Docker with --init where available.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Plan for image size, repeatability, and runtime cost

Browser binaries and their system dependencies make screenshot containers substantially heavier than a plain Node application. The documented Linux browser download estimate is approximately 282 MB; it is not a complete image-size estimate. Keep the dependency-install steps in stable Docker layers, copy package manifests before application code, and use npm ci with a committed lockfile so application package resolution is repeatable. Pin or deliberately update the base image and Puppeteer dependency together; a floating browser or image tag can change independently of your app.

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

For repeated captures, launch one browser process and open pages as appropriate for the workload rather than starting Chrome for every screenshot. Always close pages and browsers when finished, limit concurrency to what the container’s CPU and memory can support, and set navigation and operation timeouts so stuck pages do not accumulate. Full-page images and high device scale factors can increase capture time and output size. The documentation does not establish one universally optimal concurrency or timeout value, so measure those choices with the sites and resource limits you actually use.

Or skip the browser setup

If you only need a screenshot endpoint, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. Its pre-capture cleanup accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients.

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. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free and try ScreenshotNeo.

Frequently Asked Questions

Can I use Puppeteer with Docker Compose?

Yes. Use the same built image and provide the target URL, writable output mount, and any runtime settings through the service definition; the Dockerfile and browser requirements do not change.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Does Puppeteer save a screenshot automatically?

No. Call page.screenshot(); without a path, it returns bytes rather than saving a file.

Can I capture a URL that requires login?

Yes, if your application supplies the required cookies or authentication flow before capture. Protect credentials and avoid baking secrets into the image.

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.