Puppeteer Screenshot Fails in Docker: Missing Chrome Dependencies and Fixes
Diagnose Puppeteer Docker failures by error type, then fix missing libraries, browser installs, sandbox settings, and unwritable Chrome paths.
When a Puppeteer screenshot fails in Docker, first identify which startup layer failed: Linux shared libraries, the browser executable, Chrome’s sandbox, or writable profile and cache paths. Each needs a different fix. For a custom image, inspect unresolved libraries with ldd, install dependencies for the image’s exact distribution, and confirm Puppeteer launches the browser installed in that same image. Keep Chrome’s sandbox enabled when possible.
This guide uses current Puppeteer documentation, which identifies Puppeteer 25.12.0 and Node.js 22.12 or later. Package names and browser support depend on the base distribution and architecture, so treat package examples as starting points and check the matching system requirements before pinning an image.
1. Classify the failure before changing the image
Read the first meaningful Chrome or Puppeteer error in the container logs. A generic Failed to launch message can hide different causes. Match the specific symptom to the relevant layer:
| Symptom | Likely cause | First check |
|---|---|---|
error while loading shared libraries, or Chrome exits before Puppeteer connects |
A system library required by the browser is absent | Run ldd on the browser executable inside the built image |
Could not find expected browser locally or executable ENOENT |
Browser download was skipped, cache paths differ, or the configured executable path is wrong | Check the install log, runtime user, cache directory, and launch configuration |
No usable sandbox! |
Chrome cannot start its sandbox with the container’s current permissions or configuration | Review the supported sandboxed container setup and capabilities |
chrome_crashpad_handler: --database is required, profile errors, or startup failures on read-only storage |
Chrome cannot write its profile, config, or cache | Check directory writability and ownership for the runtime user |
| Chrome starts, but headless rendering or GPU behavior differs | Headless mode or GPU configuration differs from the expected environment | Only if GPU acceleration is needed, review the headless-shell GPU option |
Official references: Puppeteer troubleshooting, Puppeteer Docker guide, system requirements, and installation guide.
2. Diagnose missing shared libraries
Puppeteer’s downloaded Chrome for Testing browser can be present and still fail before Puppeteer connects if the operating system libraries it loads are absent. Run the inspection inside the final image, not only on the host:
docker run --rm --entrypoint sh your-image:tag -lc 'command -v google-chrome || command -v chrome || find / -type f -name chrome 2>/dev/null | head -n 5'
Use the path reported by the command with ldd. For example:
docker run --rm --entrypoint sh your-image:tag -lc 'ldd /path/to/chrome | grep "not found"'
Replace /path/to/chrome with the actual executable. If the command prints unresolved libraries, install the corresponding packages with the package manager for your base image. The Puppeteer troubleshooting guide lists common Debian and Ubuntu dependencies, including GTK, NSS, GBM, X11, and fonts, as well as a separate CentOS set. These are not a universal package list: package names and requirements change with distribution and release.
- Identify the exact distribution, release, and CPU architecture in the image.
- Check Puppeteer’s current system requirements and the distribution’s current Chromium or Chrome for Testing package manifest.
- Install only the matching dependencies in the Docker build.
- Rebuild the image and rerun
lddagainst the executable that the application will launch.
Do not copy an old package list from a Docker tutorial without checking it against your target release. The historical sample in Puppeteer’s troubleshooting material uses old base-image conventions and is not a current drop-in secure build recipe.
3. Fix a missing or mismatched browser executable
A missing browser executable is different from a missing shared library. Puppeteer normally downloads its browser during installation. The installation guide notes that installing puppeteer downloads Chrome for Testing and, since Puppeteer 21.6.0, chrome-headless-shell. puppeteer-core does not download a browser. Package managers or build settings that block install scripts can also prevent the download.
Check the build output and the runtime environment:
# Inside the container, inspect the runtime user and Puppeteer cache
id
printf 'HOME=%s\nPUPPETEER_CACHE_DIR=%s\n' "$HOME" "$PUPPETEER_CACHE_DIR"
find "${PUPPETEER_CACHE_DIR:-$HOME/.cache/puppeteer}" -maxdepth 4 -type f \( -name chrome -o -name chrome-headless-shell \) 2>/dev/null
- Confirm dependency installation scripts were allowed to run during the image build.
- Confirm the runtime uses the same home directory and cache location as the install step.
- Since Puppeteer 19.0.0, the default browser cache is
~/.cache/puppeteer; configurePUPPETEER_CACHE_DIRif your image needs a different stable location. - If you intentionally skip Puppeteer’s browser download or use
puppeteer-core, install a compatible browser and setexecutablePathto its real path. - Keep the Puppeteer package and browser versions compatible when pinning or managing a browser separately.
For a custom image, the essential requirement is that build time installs the browser where runtime can find it, or that runtime explicitly points to the installed browser. Avoid assuming that a browser on the host exists inside the container.
4. Choose a Docker image strategy
Custom base image
Use a custom image when you need control over the operating system, packages, application layout, or runtime user. You are responsible for installing the matching browser dependencies, ensuring the browser download or executable path is correct, and providing writable runtime directories. Revisit the package manifest when changing the distribution release, architecture, Puppeteer version, or browser build.
Official Puppeteer image
The maintained ghcr.io/puppeteer/puppeteer image includes Chrome for Testing and its required dependencies. Its Docker guide documents SYS_ADMIN for the sandboxed run and recommends --init or a custom init entrypoint to manage browser child processes. A version-specific image tag gives you a deliberate browser and Puppeteer version to update together; avoid treating a moving tag as a reproducible pin.
docker run -i --init --cap-add=SYS_ADMIN --rm ghcr.io/puppeteer/puppeteer:TAG
Replace TAG with a deliberate version tag supported by the image. Adapt the command with your application entrypoint, mounts, environment, and network settings. The capability affects the container’s security configuration; use the documented sandboxed setup that fits your deployment policy.
| Choice | What you control | What you maintain |
|---|---|---|
| Custom image | Base distribution, packages, application layout, browser installation | OS libraries, browser compatibility, cache and profile paths, child-process management |
| Official Puppeteer image | Application and runtime integration around the supplied browser | Image tag updates, sandbox capabilities, mounts, writable paths, and process management |
The official documentation provides operational requirements but no image-size, speed, or cost benchmark for these choices. Decide based on your distribution and deployment constraints, then measure your own build and runtime.
5. Keep the Chrome sandbox enabled where possible
Puppeteer strongly discourages running Chrome without its sandbox. A --no-sandbox flag can appear to fix a container permission problem, but it removes a browser security boundary. Do not make it the default remedy for No usable sandbox!.
Prefer a supported sandboxed container configuration, such as the official image’s documented run with SYS_ADMIN, after reviewing what that capability means for your environment. If a constrained environment leaves no alternative and you choose to disable the sandbox, treat that as an explicit security exception and limit the browser to trusted content. Do not send arbitrary user-supplied URLs to an unsandboxed browser.
6. Provide writable profile, cache, and config paths
Chrome writes profile and configuration data while starting. A read-only root filesystem, a home directory owned by another user, or missing writable temporary storage can cause failures that look unrelated to dependencies. Set XDG paths and Puppeteer’s user data directory to locations writable by the browser process:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
// Use a directory writable by the container's runtime user.
userDataDir: '/tmp/puppeteer-profile',
headless: true,
// Keep the sandbox enabled unless an explicit, reviewed constraint prevents it.
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: '/tmp/example.png', fullPage: true });
} finally {
await browser.close();
}
Set container environment variables to writable directories as well. For a Docker Compose service, for example:
services:
app:
image: your-image:tag
environment:
XDG_CONFIG_HOME: /tmp/xdg-config
XDG_CACHE_HOME: /tmp/xdg-cache
PUPPETEER_CACHE_DIR: /home/app/.cache/puppeteer
volumes:
- puppeteer-cache:/home/app/.cache/puppeteer
init: true
volumes:
puppeteer-cache:
Make sure the named volume and directories are owned by, or writable to, the runtime user. If you mount a read-only cache, it must already contain the compatible browser and Puppeteer must not need to modify it. Use a writable profile directory even when the browser cache is mounted separately.
7. Check version, distribution, and architecture compatibility
The current Puppeteer docs state Node.js 22.12 or later for the documented version 25.12.0. They list Chrome for Testing Linux support for Debian or Ubuntu and openSUSE or Fedora on x64 and arm64. That scope does not mean every derivative image or package manifest has the same dependencies.
- Pin compatible Node, Puppeteer, and browser versions in the image build.
- Confirm the target architecture matches the browser artifact and distribution packages you install.
- Check the current system requirements when updating Puppeteer or changing the base image.
- Do not assume an Ubuntu dependency list works on Alpine.
Alpine-specific note
Puppeteer says Chrome does not support Alpine out of the box; compatible dependencies and browser-version alignment are needed. Alpine’s Chromium behavior and packages can change. Verify the exact Puppeteer, Chromium, and Alpine versions in your target image rather than relying on old version-specific workarounds.
8. Manage browser processes and screenshot reliability
Chrome launches child processes. Use Docker’s --init option, Compose’s init: true, or an appropriate custom init entrypoint so exited child processes are reaped. Close pages and browsers in finally blocks so exceptions during navigation or capture do not leave processes running.
Make screenshot behavior predictable by setting an explicit navigation condition and timeout appropriate to the page. A page that keeps long-lived connections open may never satisfy a network-idle condition; choose a load event or wait for a specific selector when that better matches the page. For dynamic content, wait for the element that must appear rather than adding an arbitrary long sleep.
await page.goto('https://example.com', {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
await page.waitForSelector('main', { timeout: 10_000 });
await page.screenshot({ path: '/tmp/page.png', fullPage: true });
For larger workloads, cap parallel browser and page creation to the CPU and memory available to the container. Reuse a browser process when appropriate, isolate each job’s page and state, and close resources on both success and failure. These are application-level reliability practices, not a substitute for missing libraries or incorrect browser installation.
The Puppeteer troubleshooting guide notes that chrome-headless-shell needs --enable-gpu for GPU acceleration in headless mode. Apply that only when GPU acceleration is actually required; it is not a general startup fix.
9. Common errors and fixes
| Error or symptom | Cause to confirm | Fix |
|---|---|---|
error while loading shared libraries: … |
Dynamic dependency is absent | Run ldd inside the final image; install the missing library’s package for that distribution and release. |
Could not find expected browser locally |
Browser download did not run, cache moved, or runtime user differs | Inspect install-script settings and cache path; persist or configure PUPPETEER_CACHE_DIR, or set a valid executablePath. |
spawn … ENOENT |
The configured executable does not exist at runtime | Check the path in the built container, not on the host; install the browser or correct the launch path. |
No usable sandbox! |
Container permissions or sandbox setup prevent launch | Use a supported sandboxed setup and required container configuration. Avoid defaulting to --no-sandbox. |
chrome_crashpad_handler: --database is required |
Chrome cannot initialize writable crash or profile data | Provide writable profile, XDG config, and cache locations with correct ownership. |
| Works locally but not in Docker | Host libraries, browser, fonts, or cache are not present in the image | Inspect the final image and install matching dependencies; do not rely on host state. |
| Works during build but fails at runtime | Build and runtime use different users, home directories, mounts, or filesystem permissions | Compare id, HOME, cache variables, and mounted paths for both stages. |
| Navigation times out after Chrome starts | The page did not meet the chosen navigation condition before the timeout | Choose a condition suited to the page and wait for a meaningful selector when appropriate. |
10. Performance, reliability, and cost notes
- Build and image size: A browser and its OS dependencies add packages and build work. The official image bundles them; a custom image gives more control but makes you responsible for the dependency set. The cited Puppeteer docs do not publish comparative image-size or performance benchmarks.
- Version maintenance: Pin a compatible set and update it deliberately. Recheck the browser requirements when changing Node, Puppeteer, the base distribution, or architecture.
- Runtime capacity: Browser processes use memory and CPU. Bound concurrency to the container’s available resources and clean up processes after each capture.
- Filesystem reliability: Explicit writable paths prevent runtime-user changes or read-only mounts from breaking browser startup. Keep the browser cache and per-job profile responsibilities distinct.
- Security: Keep Chrome’s sandbox enabled where practical, especially when navigating untrusted pages. Treat sandbox removal as a security tradeoff.
- Cost: The research sources give no benchmark or cost comparison. Account for your own compute, image storage, dependency maintenance, and operational time.
11. Or skip the browser setup
If you need a screenshot without building and maintaining a Chrome container, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, with the page verdict and billing status in response headers. AI agents can use its MCP server tools, including take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation. This cURL request saves a WebP capture:
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 per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan. Sign up free for 1,000 screenshots a month with no card.
12. FAQ
Does installing Puppeteer automatically install Chrome?
The puppeteer package downloads its supported browser during installation by default, but install scripts can be blocked. puppeteer-core does not download Chrome; configure a browser executable yourself.
Should I use --no-sandbox in Docker?
Not as the default fix. Puppeteer discourages disabling Chrome’s sandbox. Prefer a supported sandboxed container configuration and make any exception an explicit security decision.
Can I use Alpine for Puppeteer screenshots?
Puppeteer says Chrome does not support Alpine out of the box. Verify the exact browser, Puppeteer, Alpine, and dependency versions for your target image before adopting it.
Why does the browser work in the build stage but not after deployment?
The runtime may have a different user, home or cache path, mount, executable path, or filesystem permissions. Inspect those values in the final runtime container.
Where can I find the current dependency requirements?
Start with the Puppeteer system requirements and troubleshooting guide, then verify package names against the manifest for your exact distribution and release.


