The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →To run Puppeteer headfully in Docker, launch Chrome with headless: false and provide a virtual X display. Linux containers normally have no desktop display, so start Xvfb (or invoke the process with xvfb-run), install Chrome’s shared libraries and fonts, use a browser version compatible with your Puppeteer release, and run as a non-root user whenever your container runtime allows it.
What “headful” means in a container
Headful mode starts Chrome with its normal graphical browser code paths instead of the default headless mode. Puppeteer’s setting is straightforward:
const browser = await puppeteer.launch({
headless: false,
});
The difficult part is not the Puppeteer option. A normal Linux Docker container has no physical monitor and usually no X server. Chrome therefore has nowhere to create a window and exits with a missing-display error. Xvfb (X virtual framebuffer) supplies an in-memory X display so Chrome can run without a physical screen.
Headful mode is useful when you need to reproduce headed-only behavior, inspect a browser interactively through a forwarded display, or match a workflow that depends on Chrome’s regular display stack. It does not make a container interactive by itself: you still need a way to observe the display, such as VNC or an attached debugging setup, if visual inspection is required.
#1 Best Overall
Choose the browser and container base first
Use Puppeteer’s compatible Chrome by default
Puppeteer’s downloaded Chrome for Testing is the compatibility default for a given Puppeteer release. Puppeteer guarantees that its downloaded browser works with that release; it does not guarantee arbitrary system Chrome or Chromium versions. Pin the Puppeteer version in package.json and let its install step fetch the matching browser unless you have a specific reason to manage Chrome yourself.
When a system browser is appropriate
A system-installed Chrome or Chromium can reduce duplicated downloads or fit an organization’s patching policy, but you must verify the executable path and browser compatibility yourself. A browser that starts locally can still fail in the image because required shared libraries, fonts, sandbox support, or writable directories are absent.
Pin the image and dependency set
Use a fixed Node base tag rather than an unqualified latest. Puppeteer’s maintained Dockerfile is a useful reference; its current configuration uses Node 24 Bookworm and a non-root pptruser, but that file is versioned on the project’s main branch and can change. Match its packages and Node version to the Puppeteer release you deploy instead of copying an old package list indefinitely.
One-shot jobs: Dockerfile plus xvfb-run
For a command that starts, captures a result, and exits, xvfb-run is the simplest lifecycle. The exact Debian package names can change with the base image, so treat this as a starting point and verify the packages against your selected Puppeteer and Chrome versions.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11FROM node:24-bookworm
ENV PUPPETEER_CACHE_DIR=/home/pptruser/.cache/puppeteer
XDG_CONFIG_HOME=/tmp/xdg-config
XDG_CACHE_HOME=/tmp/xdg-cache
RUN apt-get update && apt-get install -y --no-install-recommends
xvfb
ca-certificates
fonts-liberation
fonts-noto-cjk
fonts-noto-color-emoji
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN useradd --create-home --shell /bin/bash pptruser
&& mkdir -p /home/pptruser/.cache /tmp/xdg-config /tmp/xdg-cache
&& chown -R pptruser:pptruser /app /home/pptruser /tmp/xdg-config /tmp/xdg-cache
USER pptruser
CMD ["xvfb-run", "-a", "--server-args=-screen 0 1280x900x24", "node", "capture.js"]
The font packages shown cover common Latin, CJK, and emoji rendering; add the writing systems your pages actually use. Missing fonts produce layout differences even when Chrome launches successfully.
package.json
{
"private": true,
"scripts": { "capture": "node capture.js" },
"dependencies": { "puppeteer": "PIN_YOUR_VERSION" }
}
Replace PIN_YOUR_VERSION with the release you have selected, then run npm install to create a lockfile before building the image.
Runnable capture.js
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
// Do not add --no-sandbox unless you have assessed the security trade-off.
args: ['--window-size=1280,900'],
defaultViewport: { width: 1280, height: 900, deviceScaleFactor: 1 }
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 60_000
});
await page.screenshot({ path: '/tmp/example.png', fullPage: true });
} finally {
await browser.close();
}
})();
Build and run it with:
docker build -t puppeteer-headful .
docker run --rm --shm-size=1g puppeteer-headful
The larger shared-memory setting is useful for Chromium workloads that create many renderer processes. It is not a substitute for installing missing libraries or fixing a display configuration.
Long-lived workers: run Xvfb as a service
A persistent worker can start Xvfb once and reuse its display for multiple jobs. This avoids spawning a new X server for every command, but introduces process supervision, shutdown, and cleanup responsibilities. A minimal entrypoint is:
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →#!/bin/sh
set -eu
Xvfb :99 -screen 0 1280x900x24 -nolisten tcp &
XVFB_PID=$!
trap 'kill "$XVFB_PID" 2>/dev/null || true' EXIT
export DISPLAY=:99
exec node worker.js
Make the file executable and use it as the container entrypoint. In production, use a supervisor or an init process that forwards signals and reaps children. Confirm that every browser process receives the same DISPLAY. If you run parallel jobs, isolate profiles and temporary directories so one Chrome instance cannot lock or modify another instance’s user data.
Which display strategy fits?
| Strategy | Best fit | Trade-offs |
|---|---|---|
xvfb-run -a |
One-shot scripts and CI jobs | Simple lifecycle; a new virtual display is created per invocation. |
| Xvfb service | Long-lived workers and queues | Lower per-job startup overhead, but requires supervision, signal handling, display allocation, and cleanup. |
Neither approach provides a visible desktop to your laptop. Add VNC or another remote-display layer only when humans must watch or interact with the browser.
Rank #3
Sandboxing and container users
Chrome’s sandbox is a security boundary. Puppeteer’s troubleshooting guidance says running without a sandbox is strongly discouraged and limits that workaround to situations where page content is trusted. Do not reflexively add --no-sandbox just because a container launch fails.
Prefer a non-privileged user such as pptruser, and configure the container runtime, kernel user namespaces, and security policies so Chrome’s sandbox can operate. If you see “No usable sandbox!”, inspect host kernel settings, user namespaces, AppArmor or similar policy, and runtime restrictions first. Only after understanding those controls should you decide whether a narrowly scoped exception is acceptable.
Free tools Windows power users keep installed
One-click scans. No signup required.
Writable paths, libraries, and fonts
- Shared libraries: Chrome can exit before Puppeteer connects when a required system library is absent. Inspect browser stderr and install the libraries required by the Chrome build in your image.
- Writable directories: Read-only images still need writable Chrome configuration, cache, and user-data locations. Set
XDG_CONFIG_HOME,XDG_CACHE_HOME, and Puppeteer’s cache to writable paths, or pass an explicit temporaryuserDataDir. - Fonts: Install fonts for every language and symbol set you render. A missing font may silently change line breaks, element heights, and screenshot pixels.
- Temporary storage: Ensure the container has enough space for the browser cache, profiles, downloads, and screenshots. Delete per-job profiles when work completes.
Configuration details that affect reliability
Wait for the page you actually need
networkidle2 is convenient, but analytics, streaming, and long polling can keep a page busy forever. For deterministic captures, wait for a specific selector or application state and use a bounded timeout. Keep the try/finally close pattern so failed jobs still close Chrome.
Control viewport and device scale
Set the viewport explicitly. Window dimensions, device scale factor, fonts, timezone, and locale can all alter layout. If your comparison depends on stable pixels, keep the container image, browser version, font packages, and these settings pinned together.
Manage concurrency
Each browser consumes memory and renderer processes. Start with one job per browser, measure memory in your own workload, and increase concurrency gradually. Reuse a browser only when profiles, cookies, and crash recovery are isolated; otherwise launch separate contexts or processes and clean them up after each task.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting common failures
“Missing X server” or “$DISPLAY is not set”
Cause: headless: false was enabled without an X display. Fix: run xvfb-run -a node capture.js, or start Xvfb, export the matching DISPLAY, and verify that the Node process inherits it.
Chrome exits before Puppeteer connects
Cause: missing shared libraries, an unwritable profile/cache, an incompatible browser, or a killed process. Fix: read Chrome’s stderr, check writable XDG and user-data paths, verify available memory and shared memory, and use the Chrome for Testing version installed for your Puppeteer release.
“No usable sandbox!”
Cause: the container or host blocks Chrome’s sandbox. Fix: investigate kernel user namespaces and security policy, run as a non-root user, and adjust the runtime. Treat --no-sandbox as a last-resort decision for trusted content, not a generic repair.
Browser version mismatch
Cause: Puppeteer is controlling an unrelated system Chrome version. Fix: use Puppeteer’s downloaded compatible browser or explicitly configure and verify the external executable and its version. Rebuild the image after changing either dependency.
Different fonts or screenshots
Cause: the container lacks the fonts available on your development machine, or locale and device scale differ. Fix: install the required font packages and pin viewport, scale, locale, timezone, and browser versions.
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 minuteBest 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
Jobs hang during navigation
Cause: the page maintains open connections, waits on a blocked resource, or never reaches your chosen lifecycle event. Fix: set a finite timeout, wait for a meaningful selector, and log the URL, navigation phase, and browser stderr before retrying.
Or skip the browser setup
If your goal is a clean website image or PDF rather than controlling a browser session, ScreenshotNeo provides a single HTTP call. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.
Read the parameter reference in the ScreenshotNeo documentation. 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}`);
It also offers an MCP server for AI agents, including Claude and Cursor, with take_screenshot, get_page_info, and capture_pdf tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Cost, performance, and operational notes
- Startup: bundled-browser extraction, Xvfb startup, and Chrome initialization add one-time latency. A long-lived worker amortizes that cost but needs stronger process supervision.
- Memory: renderer count, page complexity, extensions, and concurrency determine consumption. Set container limits deliberately and observe out-of-memory kills.
- Reliability: pin the Node image, Puppeteer lockfile, browser build, fonts, and launch settings. Rebuild intentionally when any one changes.
- Security: keep the sandbox enabled where possible, run as non-root, restrict outbound access when appropriate, and treat arbitrary page content as untrusted.
- Debugging: preserve Chrome stderr, Puppeteer errors, URL, viewport, browser version, and display value for failed jobs. These details distinguish display, dependency, and application failures quickly.
FAQ
Can I use headful mode without Xvfb?
Only if the container has another reachable X server, such as a forwarded host display. In a typical isolated Linux container, provide Xvfb or use headless mode.
Does headful mode show a window on my host?
No. Xvfb is a virtual, in-memory display. Add a remote-display solution if a person must see the window.
Should I always pass --no-sandbox in Docker?
No. Investigate sandbox and runtime configuration first; disabling the sandbox weakens a Chrome security boundary.
Is a system Chrome supported with every Puppeteer version?
No. Puppeteer guarantees compatibility with the Chrome for Testing version it downloads for that release, not arbitrary browser versions.
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.




