ScreenshotNeo

BlogHow-to

How to run PhantomJS screenshots in a Docker container

Capture a webpage with PhantomJS in Docker: write the script, set dimensions, mount output, and account for PhantomJS’s suspended status and stale images.

By the ScreenshotNeo team4 October 20266 min read

To take a screenshot with PhantomJS in Docker, run a PhantomJS JavaScript script that opens a URL and calls page.render(), with the script and output directory available inside the container. Set viewportSize to control the browser viewport; use clipRect to restrict the captured area. PhantomJS development is suspended and its upstream repository is archived, so treat older Docker images as legacy dependencies and verify that the image tag and platform still work in your environment. [PhantomJS project, archived repository]

1. Write a PhantomJS capture script

Save this as screenshot.js. The path passed to page.render() must be writable from inside the container. This example saves to /output/capture.png, which you will mount from the host.

var page = require('webpage').create();
page.viewportSize = { width: 1024, height: 768 };

page.open('https://example.com/', function (status) {
  if (status !== 'success') {
    console.log('Could not load the page.');
    phantom.exit(1);
    return;
  }

  page.render('/output/capture.png');
  phantom.exit();
});

The script structure follows PhantomJS’s documented capture pattern: create a webpage, open a URL, render the page, and exit. Checking the open status lets a caller distinguish an unsuccessful page load from a completed capture. [PhantomJS screen-capture guide]

2. Run the script in Docker and persist the file

Provide screenshot.js to the container and bind-mount a host directory at /output. For example, if you have verified and pulled a compatible wernight/phantomjs:2.1.1 image, run:

mkdir -p output
docker run --rm \
  -v "$PWD/screenshot.js:/work/screenshot.js:ro" \
  -v "$PWD/output:/output" \
  -w /work \
  wernight/phantomjs:2.1.1 \
  phantomjs screenshot.js

This command assumes the image has a phantomjs executable on PATH and accepts that command form. Confirm the image’s current tags, architecture, entrypoint, and usage before relying on it: the Docker Hub listing surfaced for this image is old and says it was updated almost six years ago. Docker mount syntax and image behavior can also vary by host platform. [wernight/phantomjs on Docker Hub]

After the container exits, the expected host file is output/capture.png. Without the output bind mount, the file exists only in the container’s writable layer and is removed when --rm deletes that container.

3. Choose viewport, clipping, and output format

Setting Purpose Example
viewportSize Sets the browser viewport used to lay out the page. { width: 1280, height: 900 }
clipRect Selects a rectangle to capture, relative to the page. { top: 0, left: 0, width: 800, height: 600 }
Render filename extension Chooses the output format. capture.jpg or capture.pdf

Viewport and clipping solve different problems. A viewport controls the page’s browser layout; clipping limits which region is rendered. For a fixed region, add this before opening the page:

page.clipRect = { top: 0, left: 0, width: 800, height: 600 };

PhantomJS selects the render format from the filename extension. Documented formats include PDF, PNG, JPEG, BMP, and PPM. GIF output depends on the Qt build, so do not assume it is available in every image. [PhantomJS render API]

For a transparent result, the FAQ says the background can be transparent when the page does not set one. If you need an opaque image, set the page background deliberately and inspect the output. [PhantomJS FAQ]

4. Make the capture repeatable

  1. Pin and inspect the image. Check that the tag exists for your host architecture and note its entrypoint and executable path. The surfaced Docker Hub image is historical guidance, not confirmation of current compatibility.
  2. Keep inputs and outputs explicit. Mount the script read-only and mount a dedicated output folder. Use absolute paths inside the container to avoid working-directory surprises.
  3. Check the page-open result. The callback status can report failure; exit nonzero so automation can detect it.
  4. Control capture geometry. Set viewport dimensions for the intended layout, then use clipping only when you need a region rather than the page render.
  5. Keep TLS validation enabled. PhantomJS documents --ignore-ssl-errors with a default of false. Disabling certificate checks should not be a routine workaround; fix the certificate or trust configuration instead. [PhantomJS command-line documentation]

PhantomJS versions 1.5 and later are documented as pure headless and do not require X11/Xvfb. That does not guarantee a particular container image will run on every current host. The non-root UID/GID description in the Docker Hub listing applies to that image listing and should not be generalized to custom images. [PhantomJS FAQ, Docker Hub listing]

5. Troubleshoot common failures

Symptom Likely cause What to check
phantomjs: not found The image does not put the executable on PATH, uses a different entrypoint, or the selected tag is unavailable. Inspect the image documentation and run its documented command. Verify the tag and platform before using it in automation.
The container exits but no host file appears The output path is not mounted where the script writes, or the process lacks permission to write there. Match page.render() to the container mount destination, confirm the host folder exists, and check its ownership and permissions.
The script cannot be found The script mount or working directory does not match the command path. Confirm the host file path, container mount destination, and -w directory.
The output is blank or the target did not load The open operation failed or the page was not available to the legacy browser. Check the callback status and container network access. PhantomJS is an old WebKit-based browser; modern page behavior may not render as expected.
Image dimensions differ from expectation Viewport dimensions and clip dimensions were confused, or the page layout responds to the chosen viewport. Set viewportSize explicitly and use clipRect only for the desired output region.
JPEG, PDF, or GIF output is missing or wrong The filename extension may not match the desired format; GIF support can vary by Qt build. Use a documented extension and confirm build-specific format support.
TLS or certificate errors The container does not trust the site’s certificate chain or has a certificate problem. Repair the certificate or trust setup. Avoid turning off SSL checks as a default fix.
Permission denied while writing The process user cannot write to the mounted directory. Adjust host directory ownership or permissions for the image’s runtime UID. Do not assume all PhantomJS images use the same UID.

6. Performance, reliability, and cost

Containerizing PhantomJS gives a repeatable place to run a capture script, but it does not make the browser current or ensure modern sites render correctly. PhantomJS development is suspended, its repository is archived, and the surfaced Docker image is stale. For a retained legacy workflow, verify the exact rendering behavior and platform compatibility your application depends on; evaluate maintenance and security requirements as part of that decision. [PhantomJS project, archived repository, Docker Hub listing]

Capture cost is the compute and storage you operate: container runtime, image distribution, network access, and retained output files. The research sources establish no performance benchmarks or current replacement comparison, so measure your own pages and do not infer throughput from this example. Keep jobs bounded by your orchestration system’s timeout and clean up generated files according to your retention needs.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. Instead of maintaining a PhantomJS container, make one request to the ScreenshotNeo API:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

ScreenshotNeo can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.

Sign up free for 1,000 screenshots a month, no card required.

FAQ

Does PhantomJS need Xvfb in Docker?

PhantomJS documentation says versions 1.5 and later are pure headless and do not require X11/Xvfb. Check the version and packaging of the specific image you use.

Can I capture only part of a page?

Yes. Set page.clipRect to the region to render. Set the viewport separately to control page layout.

Can I use PhantomJS for a new screenshot service?

It can run legacy scripts, but its development is suspended and its upstream repository is archived. Verify browser compatibility, image freshness, platform support, and maintenance requirements before choosing it for a new long-lived workflow.

What file formats can I render?

The extension selects the format. The documented options include PDF, PNG, JPEG, BMP, and PPM; GIF depends on the Qt build.