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.
#1 Best Overall
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:
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minuteimport 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.
Rank #2
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.
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.
Rank #3
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.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →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:
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →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. |
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.
Best Value
- 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.
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.
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.

