How to Deploy Playwright on GCP Compute Engine
Deploy a version-pinned Playwright browser on a Google Cloud VM with Docker, startup scripts, firewall controls, remote access, and production troubleshooting.

Direct answer: For a straightforward Compute Engine deployment, run a version-pinned Playwright Docker image on a Linux VM, start the container with a startup script or cloud-init, and expose only the service port that trusted clients need. Keep the Playwright package version aligned with the browser image version. Use --init and, for Chromium, --ipc=host. If the work belongs to a build pipeline rather than a persistent service, use Playwright’s Google Cloud Build pattern instead of maintaining a VM.
This guide explains how to run Playwright on a Google Cloud VM, how to run Playwright in Docker on Compute Engine, and how to connect to a remote Playwright server. The examples are deployment patterns derived from the official documentation; no deployment, benchmark, or cost test is claimed.
1. Choose the right workload shape
A persistent VM makes sense when you need a long-running browser service, remote browser access, custom OS packages, or a host you can administer directly. A CI job is usually a better fit when browsers run only during builds or tests. Playwright documents Google Cloud Build usage with its public image, while Google Cloud also describes Cloud Run for stateless containers, Batch for jobs with a defined end, and GKE for larger orchestrated systems. Choose based on lifecycle, concurrency, and operational needs; the sources do not provide a Playwright-specific cost comparison.
| Need | Reasonable starting point | Watch for |
|---|---|---|
| Persistent or remotely accessed browser | Compute Engine VM | Patching, capacity, firewall exposure |
| Tests tied to a build | Cloud Build job | Job startup and artifact handling |
| Stateless HTTP browser workload | Cloud Run | Request time limits and concurrency |
| Many services or replicas | GKE or a managed instance group | Orchestration and health management |
2. Pin compatible Playwright versions
The Playwright Docker image contains browser binaries and operating-system dependencies, but your application still installs the Playwright package. The package and image must match closely enough for Playwright to find the browser executables. Pin both instead of using floating tags.
The Docker documentation currently shows examples using v1.63.0, including the Ubuntu 24.04 based v1.63.0-noble image. Treat that as a documentation snapshot and check the current supported tag before you build.
# package.json (example)
{
"private": true,
"dependencies": {
"playwright": "1.63.0"
}
}
You can start from the official image:
FROM mcr.microsoft.com/playwright:v1.63.0-noble
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
Or build your own base image when you need a different operating-system layout. The documented custom-image approach starts with Node.js and runs npx playwright install --with-deps. Whichever route you choose, keep the package and browser versions aligned and record the chosen versions in source control.
3. Create the Compute Engine VM
- Create a Linux Compute Engine instance using a maintained public image or your organization’s approved image.
- Attach a service account with only the permissions the startup process needs.
- Install Docker using your approved provisioning method, or select an image that already includes your container runtime.
- Choose a machine size based on your own page complexity and concurrency measurements. The reviewed sources provide no universal CPU, memory, throughput, or cost recommendation.
Keep browser workloads isolated from unrelated services. Chromium can consume substantial shared memory and temporary disk space, especially when several pages run at once. Set explicit concurrency limits in your application and monitor memory, disk, and process counts.

4. Start the container with a startup script
Google defines a startup script as “a file that contains commands that run when a virtual machine (VM) instance boots.” On Linux, the guest environment reads startup-script metadata and executes it when the network is available. Public Compute Engine images include that guest environment; custom images may require you to install it.
Use a VM metadata startup script or cloud-init for new deployments. Do not choose the deprecated Compute Engine container startup agent or legacy Deploy container workflow.
#!/bin/bash
set -euo pipefail
IMAGE="REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/playwright-service:1.0.0"
# Pull a versioned image and replace an old container during boot.
docker pull "$IMAGE"
docker rm -f playwright-service 2>/dev/null || true
docker run -d \
--name playwright-service \
--restart unless-stopped \
--init \
--ipc=host \
-p 3000:3000 \
"$IMAGE"
Store the script in VM metadata or a tightly controlled Cloud Storage location. Startup scripts run as root on Linux, so anyone who can modify the script can potentially execute privileged commands at the next reboot. Restrict write access to the script, protect the bucket if you use one, and avoid placing API keys directly in metadata. Retrieve secrets through an approved secret-management path and grant the VM service account only the required access.
5. Configure networking and firewall rules
For the documented Compute Engine container networking model, containers use the VM host network stack. External access is controlled by the VM’s firewall rules and the protocol and port you allow. Do not assume Docker’s usual published-port mental model gives you a public endpoint automatically.
If your application listens on port 3000, create an ingress rule only for the trusted source range:
gcloud compute firewall-rules create allow-playwright-from-ci \
--network=default \
--direction=INGRESS \
--action=ALLOW \
--rules=tcp:3000 \
--source-ranges=203.0.113.0/24 \
--target-tags=playwright-vm
Apply the matching network tag to the VM. Replace the example range with your private network, VPN, or CI egress range. Avoid opening a remote browser-control port to 0.0.0.0/0. A browser server that accepts arbitrary clients can become a path to internal systems or an abuse target.
6. Run a remote Playwright server
Playwright documents a server container listening on port 3000. Remote clients can connect with PW_TEST_CONNECT_WS_ENDPOINT or with browserType.connect(). The client and server should use matching Playwright versions.
docker run --rm \
--init \
--ipc=host \
-p 3000:3000 \
mcr.microsoft.com/playwright:v1.63.0-noble \
/bin/sh -c "npx playwright run-server --port 3000 --host 0.0.0.0"
From a trusted client, connect over a private route:
import { chromium } from 'playwright';
const browser = await chromium.connect('ws://VM_PRIVATE_IP:3000/');
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
console.log(await page.title());
await browser.close();
Use a private IP, VPN, or an internal load-balancing design where possible. The example server does not provide an authentication layer by itself. Put authentication and transport protection in front of it, or keep it reachable only from trusted network paths. Do not expose the documented port publicly just because the sample uses 0.0.0.0 inside the container.
7. Browser-container settings that prevent common failures
--init: Playwright recommends this to handle PID 1 behavior and reduce zombie processes.--ipc=host: Chromium benefits from larger shared memory; constrained shared memory can cause browser crashes.- Headless mode: Playwright launches headless by default.
- Headed mode: Linux headed execution requires Xvfb. The Playwright image includes it, and you can wrap a command with
xvfb-run. - Untrusted targets: For crawling or scraping arbitrary sites, use a separate non-root user and a seccomp profile. Playwright says its Docker image is intended for testing and development and is not recommended for visiting untrusted websites.
xvfb-run -a node headed-workflow.js
8. Verify the deployment
- Check that the VM reached the expected network state and that the startup script completed.
- Inspect the container state and logs.
- Run a small browser check against a known, trusted page.
- Confirm that only the intended firewall sources can reach the service.
- Exercise shutdown and reboot behavior so the restart policy and startup script are known to work.
gcloud compute instances get-serial-port-output VM_NAME --zone=ZONE
docker ps
docker logs --tail=200 playwright-service
curl -I http://VM_PRIVATE_IP:3000
Keep your own health check separate from the browser-control endpoint when possible. A health check can report whether the application is ready without granting callers browser access.
9. Troubleshooting
“Executable doesn’t exist” or browser launch failures
Cause: The npm package and Docker image versions differ, or browsers were never installed in a custom image.
Fix: Pin matching versions. For a custom image, run npx playwright install --with-deps during the image build.
Chromium crashes under parallel work
Cause: Shared memory is too small, or the VM is oversubscribed.
Fix: Add --ipc=host, reduce concurrency, and inspect memory and disk pressure. Do not infer a universal VM size from another workload.
Startup script did not run
Cause: The guest environment is missing on a custom image, the script has syntax or permission errors, or network access was unavailable when it ran.
Fix: Read serial-port output, verify the metadata key, use a shell with set -euo pipefail, and confirm the guest environment is installed.
Remote clients cannot connect
Cause: The process is listening only on loopback, the firewall rule does not match the VM tag, the source range is wrong, or the client and server versions differ.
Fix: Bind the server to the intended interface, check the VM tag and firewall rule, test from the allowed network, and align Playwright versions.
Requests time out while pages work locally
Cause: DNS, egress restrictions, target-side bot checks, slow resources, or a page that never reaches the selected wait condition.
Fix: Test DNS and outbound connectivity from the VM, set explicit navigation and action timeouts, choose a realistic readiness condition, and capture logs for the target site. Do not disable security controls broadly to make one page pass.
Zombie browser processes accumulate
Cause: The container was started without an init process or the application is not closing contexts and browsers.
Fix: Run with --init, close each context in a finally block, and monitor process counts.
10. Reliability, performance, and cost planning
Reliability comes from controlling lifecycle and failure boundaries: pin images, use a restart policy, make startup idempotent, keep browser work bounded by timeouts, and send logs to your normal observability system. A single VM remains a single failure domain. If availability requirements justify it, consider multiple instances or a managed instance group, while recognizing the added operational complexity.

Performance depends on page weight, JavaScript execution, viewport, browser choice, concurrency, and network distance. Measure your own workload. Track navigation duration, browser launch time, memory per page, crash count, queue depth, and successful job rate. The reviewed sources contain no deployment-specific benchmark or cost number, so avoid promising a fixed throughput or monthly bill.
For CI-only execution, compare the operational cost of a persistent VM with Cloud Build jobs. For a persistent service, include VM runtime, disk, logging, image storage, network egress, and the engineering time required to patch browsers and the host.
Or skip the browser setup
If your goal is simply to obtain a clean screenshot or PDF from a URL, ScreenshotNeo provides a hosted GET endpoint and an MCP server, so you do not need to maintain a Playwright VM. Read the ScreenshotNeo API documentation for all 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 and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and whether it was billed. Options include full-page capture with lazy images loaded, CSS selector element capture, dark mode, device presets, custom viewport and retina scale, PDF settings, custom CSS and JavaScript, clicks, waits, blocked requests, headers, cookies, user agent, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture for up to 100 URLs per call, and a usage API. 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 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
FAQ
Can I install Playwright directly on the VM instead of Docker?
Yes, but you must install compatible browser system dependencies and manage browser updates yourself. A pinned image usually makes those dependencies more repeatable.
Should I expose port 3000 through an external IP?
Only when a trusted client requires it and the firewall is restricted. Prefer private networking or a protected proxy for remote browser control.
Is Compute Engine suitable for scraping arbitrary websites?
It can host the process, but arbitrary targets are security-sensitive. Follow Playwright’s guidance on a non-root user and seccomp profile, and remember that the documented image is intended for testing and development.
What replaces the old Compute Engine container startup agent?
Use a startup script or cloud-init for new VM container deployments, as described in Google’s current guidance.
When should I use Cloud Build?
Use it when browser execution is part of a build or test job and you do not need a persistent, remotely reachable browser service.


