Run Chrome headless on Cloud Run by packaging Chromium and its Linux dependencies in a container, then controlling it with Puppeteer, Playwright, or the Chrome DevTools Protocol. For a small Puppeteer screenshot service, the shortest route is Puppeteer’s official Docker image: it includes Chrome for Testing and the required dependencies. Cloud Run does not include the Chrome system packages in its default Node.js runtime.
Choose a browser setup that fits Cloud Run
Cloud Run runs a Linux container, not a ready-made desktop browser environment. Your image must contain a compatible browser executable and runtime libraries. Cloud Run accepts OCI and Docker images and requires Linux 64-bit executables. First-generation services use gVisor sandboxing; second-generation services provide full Linux compatibility. Check the execution environment you plan to use against your browser image and its sandbox needs.
There are three practical control layers: Puppeteer, Playwright, and the Chrome DevTools Protocol (CDP). Google’s Cloud Run browser guidance names all three as options. For a simple Chromium screenshot endpoint, Puppeteer is a concise starting point; choose based on browser coverage, the API your team knows, image size and update cadence, sandbox compatibility, concurrency needs, and whether you depend on Chromium-specific behavior.
| Option | What it provides | When it fits |
|---|---|---|
| Puppeteer | JavaScript API for browser automation; its official Docker image includes Chrome for Testing and required dependencies. | Chromium-driven screenshots, PDFs, extraction, and automation when its API fits your application. |
| Playwright | Supports Chromium, WebKit, Firefox, Google Chrome, and Microsoft Edge. Its distribution includes a regular Chromium build and a separate headless shell. | Workloads that need more than Chromium or prefer Playwright’s browser automation API. |
| CDP | Direct control through Chrome DevTools Protocol. | Teams that want to work at the protocol level instead of using a higher-level browser library. |
Playwright’s Docker guidance also documents Microsoft-provided images and explains that sandboxed Chromium may require a seccomp profile that permits user-namespace operations. That detail matters if you choose Playwright’s sandboxed setup; do not assume every browser image has identical runtime requirements.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
Build a minimal Puppeteer screenshot service
This example accepts a URL at POST /screenshot, opens it in Chrome, and returns a full-page PNG. It uses the Puppeteer Docker image so you do not have to assemble a list of system libraries yourself. The image reference below follows Puppeteer’s documented example; for a production deployment, select and maintain an image version deliberately rather than treating a moving latest tag as a release pin.
1. Create the application files
Create package.json:
{
"name": "cloud-run-headless-shot",
"version": "1.0.0",
"private": true,
"main": "server.js",
"scripts": { "start": "node server.js" },
"dependencies": { "puppeteer": "^24.0.0" }
}
Create server.js:
const http = require('node:http');
const puppeteer = require('puppeteer');
const server = http.createServer(async (req, res) => {
if (req.method !== 'POST' || req.url !== '/screenshot') {
res.writeHead(404, { 'content-type': 'text/plain' });
return res.end('Not found');
}
let raw = '';
for await (const chunk of req) raw += chunk;
let target;
try {
target = new URL(JSON.parse(raw).url);
if (!['http:', 'https:'].includes(target.protocol)) throw new Error('scheme');
} catch {
res.writeHead(400, { 'content-type': 'text/plain' });
return res.end('Send JSON with a valid http or https url');
}
let browser;
try {
browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1365, height: 900 } });
await page.goto(target.href, { waitUntil: 'networkidle2', timeout: 30000 });
const png = await page.screenshot({ type: 'png', fullPage: true });
res.writeHead(200, { 'content-type': 'image/png' });
res.end(png);
} catch (error) {
console.error('Capture failed:', error);
if (!res.headersSent) {
res.writeHead(502, { 'content-type': 'text/plain' });
res.end('Browser could not capture the requested page');
}
} finally {
if (browser) await browser.close().catch(console.error);
}
});
server.listen(process.env.PORT || 8080, '0.0.0.0');
This deliberately small handler validates the URL scheme, but scheme validation alone is not sufficient protection for a public URL-fetching service. Restrict which destinations callers can request and apply network egress controls appropriate to your environment; otherwise a caller may try to make the browser reach internal services. Add authentication and request-size limits before exposing the endpoint beyond a trusted environment.
2. Add the Dockerfile
FROM ghcr.io/puppeteer/puppeteer:latest
WORKDIR /home/pptruser/app
COPY --chown=pptruser:pptruser package.json ./
RUN npm install --omit=dev
COPY --chown=pptruser:pptruser server.js ./
ENV NODE_ENV=production
CMD ["node", "server.js"]
The image supplies Chrome and its required dependencies; the application installs Puppeteer as its Node dependency. Keep the browser and Puppeteer versions compatible when you update either. If you instead build from a general Node.js image, you must install Chromium and its system dependencies in that image: the default Cloud Run Node.js runtime does not provide them.
3. Build, deploy, and call the service
Build and push the container to a registry available to your Google Cloud project, then deploy it as a Cloud Run service. Replace the uppercase placeholders with your project’s actual values:
Recommended Free Tools
docker build -t REGION-docker.pkg.dev/PROJECT/REPOSITORY/headless-shot:1 .
docker push REGION-docker.pkg.dev/PROJECT/REPOSITORY/headless-shot:1
gcloud run deploy headless-shot
--image REGION-docker.pkg.dev/PROJECT/REPOSITORY/headless-shot:1
--region REGION
--memory 1Gi
--concurrency 1
--timeout 120
Cloud Run supplies the listening port through PORT; the server above listens on it and binds to 0.0.0.0. The example starts conservatively at concurrency one because it launches a separate browser for each request. Treat memory, timeout, and concurrency as workload settings to measure and tune, not universal browser defaults. Send a test request to the service URL returned after deployment:
curl -X POST "SERVICE_URL/screenshot"
-H "Content-Type: application/json"
-d '{"url":"https://example.com"}'
-o page.png
A successful request returns PNG bytes saved as page.png. A non-2xx response means the handler rejected the input or the browser navigation/capture failed; inspect the Cloud Run logs for the underlying browser error.
Adapt the capture to the job
Wait behavior and timeouts
The example uses networkidle2, which waits for network activity to settle. Pages with analytics, streaming connections, or long-running requests may never reach that condition before the navigation timeout. For those pages, use a more suitable navigation condition such as domcontentloaded, then wait for the specific content your task needs. A fixed delay can help with a known animation or late-loading widget, but it is less reliable than waiting for a meaningful selector. Set navigation waits and the Cloud Run request timeout so a slow page has a clear upper bound.
Screenshot, PDF, and extraction
For a viewport-only image, omit fullPage: true from page.screenshot(). For a PDF response, use Puppeteer’s PDF capture and return the matching content type; for extraction, read the page’s DOM after the required content is present. Each output needs its own response headers and validation. The example does not implement PDF options, selector-specific waits, or extraction, so add those to the handler rather than assuming they happen automatically.
Browser lifetime and request concurrency
Launching one browser per request keeps the example’s cleanup simple, but adds launch overhead. A bounded browser pool can reduce repeated startup work at higher request volume, yet it also requires explicit limits on pages, cleanup, stale-browser recovery, and memory use. Close every page and browser when its work ends. Begin with low concurrency, monitor memory and failure rates under representative pages, then increase concurrency only when the service has headroom.
Sandboxing, trust, and background work
Do not make --no-sandbox the default
Chrome’s sandbox is an important isolation layer. Puppeteer documents --no-sandbox as a fallback when no usable sandbox is available, but only for content the operator fully trusts. Disabling it is a security trade-off, not a standard Docker fix. First validate the Cloud Run execution environment, container permissions, and browser image. If sandboxed Chromium is required, confirm that the environment permits the operations it needs; Playwright’s Docker guidance specifically calls out user-namespace operations and seccomp configuration.
Finish work before responding, or allocate CPU for background work
Cloud Run’s HTTP service model is simplest when browser work finishes before the response is sent. If the process continues browser work after returning a response, CPU allocation matters: Cloud Run may suspend CPU, and Puppeteer’s troubleshooting guide reports an apparent Chrome launch delay of 1–5 minutes in that situation. That is a documented operational warning, not a general performance benchmark. For genuine post-response processing, enable CPU always allocated and design the task lifecycle accordingly; otherwise return only after the capture is complete.
For a long-running process or browser sandbox use case, Google Cloud also describes a sandboxed code execution feature. It is Preview and subject to Pre-GA terms; detached sandboxes are intended for long-running processes, headless browsers, and background servers. Treat that as a distinct, preview offering, not a prerequisite for an ordinary Cloud Run HTTP screenshot service.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
Troubleshoot common failures
- Chrome fails to launch with a missing-library or executable error: the runtime image lacks the browser or a required dependency. Use a complete browser image such as Puppeteer’s documented image, or install Chromium and its runtime libraries in your custom image. Rebuild and redeploy; installing a package locally does not add it to an already-built container.
- Launch or navigation times out: distinguish browser startup from page loading in logs. Check memory pressure, the navigation wait condition, the page’s response, and the configured service timeout. Pages that keep network activity open may not satisfy
networkidle2; wait for a selector or use a different condition when appropriate. - It works locally but not on Cloud Run: local machine libraries and sandbox permissions may differ from the deployed Linux container and execution generation. Test the actual image and Cloud Run environment, not only a locally installed Chrome.
- Chrome appears to start minutes after the request already returned: the browser work may be running after the HTTP response while CPU is not allocated. Keep the operation inside the request or enable always-allocated CPU for a real background job.
- Memory use or errors rise when requests overlap: each browser process consumes resources. Reduce concurrency, ensure every page and browser closes in a
finallypath, and only move to a bounded pool after measuring the workload. - A sandbox error tempts you to add
--no-sandbox: verify permissions and sandbox support first. Use that flag only when the content is fully trusted and you accept the loss of an isolation layer.
Or skip the browser setup
If your task is simply to get a website screenshot, ScreenshotNeo offers a screenshot API and MCP server instead of requiring you to operate Chrome in Cloud Run. One GET request returns an image or PDF. For example, save a WebP screenshot with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Cookie and consent banners are accepted before capture, and 60+ known consent platforms, newsletter popups, and chat widgets can be removed; each step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses include X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. See ScreenshotNeo for the service details, and sign up free for 1,000 screenshots a month with no card.
When Cloud Run is not the right browser environment
Cloud Run is a good fit for request-driven browser tasks such as screenshots, PDFs, form submission, UI tests, scraping, and data extraction. If the task depends on file uploads or downloads, browser extensions, or complex drag-and-drop journeys, Google describes a full desktop operating system with VNC streaming as an alternative. Those workflows need a different environment than a headless HTTP service.
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.
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 minute

