How to run Chrome Headless screenshots in a Docker container
Capture a webpage with Chrome Headless in Docker, preserve the PNG outside the container, and troubleshoot viewport, wait, sandbox, and dependency issues.
Run Chrome’s Headless command line with --screenshot inside the container. Chrome saves screenshot.png in its current working directory by default. To keep the image after the container exits, make that directory a Docker bind mount or another persistent output location.
docker run --rm \
--mount type=bind,src="$PWD/output",dst=/screenshots \
--workdir /screenshots \
YOUR_CHROME_IMAGE \
chrome --headless --screenshot --window-size=1280,800 --timeout=10000 https://example.com
Replace YOUR_CHROME_IMAGE with an image that has a working Chrome or Chromium binary, and adjust the executable name if needed. This command uses the current --headless mode, captures a 1280 by 800 viewport, and allows up to 10 seconds before capturing. The resulting file is output/screenshot.png on the host.
Chrome’s command-line reference documents --screenshot, --window-size, and --timeout; the screenshot is written to the current working directory. Chrome Headless documentation.
1. Understand the container and output path
A container’s writable layer is often discarded when a run-and-remove workflow exits. A successful capture can therefore disappear unless Chrome writes into a mounted directory or the output is copied elsewhere before the container is removed.
| Setting | What it controls | Practical effect |
|---|---|---|
--screenshot |
Enables screenshot capture | Writes screenshot.png in Chrome’s current working directory |
--window-size=WIDTH,HEIGHT |
Viewport dimensions in pixels | Sets the visible browser area used for a viewport capture |
--timeout=MILLISECONDS |
Maximum wait before capture | Bounds the wait, but does not guarantee app-specific work has completed |
--workdir in Docker |
Container working directory | Can place Chrome’s default output directly in the mounted directory |
| Docker bind mount | Maps a host directory into the container | Makes the resulting file accessible after the container exits |
Create the host output directory before running the example. On Linux or macOS, mkdir -p output is sufficient. In a Windows shell, use that shell’s path syntax for the source of the mount; Docker mount syntax depends on the host shell and environment.
2. Run Chrome Headless in Docker
One-off capture with an existing image
If your image already includes Chrome and its required runtime libraries, mount a host directory and use it as the working directory:
mkdir -p output
docker run --rm \
--mount type=bind,src="$PWD/output",dst=/screenshots \
--workdir /screenshots \
YOUR_CHROME_IMAGE \
chrome --headless \
--screenshot \
--window-size=1280,800 \
--timeout=10000 \
https://example.com
After the command finishes, check for output/screenshot.png. If the image uses another binary name, such as chromium, substitute that executable. Confirm the name from the selected image’s setup instructions.
Dockerfile pattern for a prepared image
There is no single dependency list that applies to every Chrome build, operating system base, CPU architecture, locale, and font requirement. Start from an image whose current instructions document a compatible browser installation, then set a mounted output directory as the working directory. This Dockerfile pattern deliberately leaves the image choice to that documented setup:
FROM YOUR_CHROME_IMAGE
WORKDIR /screenshots
# Provide this directory as a bind mount at runtime.
CMD ["chrome", "--headless", "--screenshot", "--window-size=1280,800", "--timeout=10000", "https://example.com"]
Build it with docker build -t chrome-shot ., then run it with a mount:
mkdir -p output
docker run --rm \
--mount type=bind,src="$PWD/output",dst=/screenshots \
chrome-shot
Use the browser image’s documented user and entrypoint behavior. Some images set a user or entrypoint already; check those details before adding Docker flags or overriding the entrypoint.
3. Choose the right capture behavior
Viewport screenshots
The documented basic command produces a screenshot of the target page at the specified viewport. Change the dimensions to match the layout you need, for example:
chrome --headless --screenshot --window-size=1440,900 --timeout=10000 https://example.com
A viewport screenshot is useful for checking a page at a particular browser size. It should not be treated as a full-page capture: the basic screenshot and viewport flags do not establish one universal full-page command for all Chrome versions and wrappers.
Waits and page readiness
--timeout places a maximum bound on how long Chrome waits before capture. It does not mean that a site’s scripts, asynchronous data, fonts, lazy-loaded media, or animations have definitely finished. If the image must reflect a specific application state, use a browser automation approach with an explicit readiness condition, or arrange for the page to be ready before invoking the capture.
Headless versions
Use --headless for current Chrome Headless. Older examples may use --headless=new or refer to --headless=old. Starting with Chrome M132, the old Headless implementation is no longer part of the Chrome binary. If a specific compatibility need requires that old implementation, Chromium documents the separate chrome-headless-shell binary. Pin the browser version and verify the exact binary behavior you require. Chrome’s old Headless removal announcement.
4. Sandbox, display server, and runtime setup
- No Xvfb is needed for Headless mode. Headless Chrome does not need a display server.
- Run as a configured non-root user where possible. Chrome’s sandbox should be retained when the container is set up to support it.
- Do not add
--no-sandboxautomatically. Chrome documentation says it is not needed when the container is properly configured with a user. If you believe your environment requires disabling the sandbox, first assess the isolation and security implications for that deployment. - Check the chosen image’s dependencies. Required libraries, fonts, architecture support, and locale configuration vary across builds; the available official references do not define a universal package list.
For stable output across environments, pin the browser and container image versions. Also make the locale and fonts consistent when they affect rendered text or layout. Treat these as deployment reproducibility choices, not Chrome screenshot flags.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No screenshot remains after the container exits | The file was written to the container’s temporary writable layer | Set a mounted output directory as the working directory, or copy the file to persistent storage before exit |
screenshot.png is missing |
Chrome used a different working directory, the mount points elsewhere, or capture failed | Set Docker’s --workdir to the mount destination; inspect the container output and confirm the Chrome command completed successfully |
chrome: not found |
The image lacks Chrome or uses a different executable name | Follow the image’s current browser setup instructions and use its installed binary name |
| Chrome exits with a missing library error | The selected image does not include a runtime dependency required by that browser build | Use the browser provider’s documented compatible image setup; avoid assuming package lists transfer across base images or architectures |
| Chrome refuses to run as root or reports a sandbox problem | The container user or sandbox setup is incompatible with the browser’s security model | Configure a non-root user and preserve the sandbox where possible. Do not default to --no-sandbox; evaluate the deployment’s isolation if considering it |
| Screenshot is blank or incomplete | The page may not have loaded, may require asynchronous work, or may have encountered a site-side challenge | Inspect Chrome’s output, confirm network access, increase the bounded timeout where appropriate, and use an application-specific readiness condition when necessary |
| Layout differs from the expected result | Viewport size, device assumptions, fonts, locale, or browser version differ | Set the intended viewport, pin the browser and image, and provide consistent fonts and locale configuration |
| An old Headless option is rejected or behaves differently | The example targets an older Chrome generation | Prefer --headless; for an explicit need for the old implementation on M132 and later, investigate chrome-headless-shell |
6. Performance, reliability, and cost
The command-line approach has no separate screenshot API fee, but you operate the browser image, container runtime, output storage, and any infrastructure that runs captures. Capture time and resource use depend on the target page and deployment; the reviewed sources do not support a universal benchmark or a claim that one Headless binary is faster.
- Bound waits: choose a timeout suitable for the page and environment so a slow load does not wait indefinitely.
- Make output durable: mount or export the output path and verify the artifact exists before downstream jobs consume it.
- Pin versions: keep Chrome and the container image stable when repeatability matters, and update deliberately.
- Handle failures explicitly: capture the process exit status and logs in automation; a missing or unusable image should fail the pipeline rather than silently pass.
- Budget the full workflow: include container compute, browser maintenance, and artifact storage in operational cost estimates. No source here provides a cross-environment cost or speed figure.
7. Or skip the browser setup
ScreenshotNeo offers a screenshot API and MCP server. One GET request returns a screenshot or PDF; see the API documentation for parameters and options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers say the page verdict and billing status.
- An MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots.
- 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots.
ScreenshotNeo also supports full-page and element captures, device presets and custom viewports, dark mode, PDF options, custom CSS and JavaScript, waits, request blocking, custom headers and cookies, caching, signed image links, async jobs, bulk capture, and a usage API. See ScreenshotNeo for the service overview and the docs for configuration. Sign up for 1,000 free screenshots a month, with no card required.
8. Frequently asked questions
Does Chrome Headless in Docker need Xvfb?
No. Headless Chrome does not need a display server such as Xvfb.
Where does Chrome save the screenshot?
By default, Chrome writes screenshot.png in its current working directory. In Docker, mount that directory if the file needs to survive the container.
Does --timeout wait until every page script finishes?
No. It bounds the wait before capture. It does not guarantee that application-specific asynchronous work is complete.
Is --no-sandbox required?
No. Configure the container with a user and retain the sandbox where possible. The correct deployment setup depends on the selected image and runtime.
Can I use this command for a full-page screenshot?
The documented basic --screenshot and --window-size flags establish a viewport capture. Full-page capture requires a separate method appropriate to the Chrome version or automation wrapper.


