Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
browser automation

How to Run Puppeteer in Headful Mode in Docker (with Xvfb)

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

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.

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

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
FROM 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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/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.

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.

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

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 temporary userDataDir.
  • 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.Support on Ko-Fi

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.

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

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.

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

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.

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

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.

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

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.