PDFPipe

Managed containers / Google Cloud Run

Playwright on Google Cloud Run: a two gigabyte image on a cold start

Everything works, and the first request after a quiet period takes many seconds before any of your code runs.

What is actually wrong

The official Playwright image carries three browser engines and their system dependencies. On a platform that scales to zero, the image has to be pulled and started before the first request is served, so image size converts directly into the latency of the request that happens to arrive first. This is not a crash and no error is logged, which is why it usually gets diagnosed as a slow endpoint rather than as a deployment shape.

What Google Cloud Run gives you to work with

your container image, started on demand and scaled to zero, with requests routed into it.

  • Packaging: no meaningful image size limit. Image size shows up as cold start latency instead, because the image has to be pulled before the first request is served.
  • Filesystem: the container filesystem is writable, but writes go to memory and count against the instance memory limit. A process that writes a large temporary file is really allocating memory, and it will be killed for it.
  • Processes: full process control inside the container. The default request concurrency is high, which means many simultaneous renders inside one instance rather than one per instance.

The fix, if you are keeping Playwright

Install only the browser you use, on a slim base, rather than starting from the full image. Keep the dependency installer, because working out which system packages Chromium needs by hand is how you end up with an image that runs on your machine and not in the cluster.

dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
# One browser, and the system packages it actually needs.
RUN npx playwright install --with-deps chromium
COPY . .
CMD ["node", "server.js"]

What still hurts after that

A slim image is still hundreds of megabytes, so cold starts improve rather than disappear. Setting a minimum instance count removes the cold start and removes the scale-to-zero billing that was the reason for choosing this platform. The version of Playwright and the version of the browser it installs are coupled, so the image has to be rebuilt on every library upgrade or the browser is silently redownloaded at startup.

What Playwright is genuinely good at

a better API than Puppeteer for waiting on real page state, and a first-party dependency installer that knows which system packages the browser needs. That is worth keeping in view: the problem on this page is where it runs, not what it produces. Playwright is a browser automation library that installs its browsers outside node_modules, into a shared cache directory, at install time. It needs the same browser and shared libraries as Puppeteer, plus an install step that runs separately from the package install and writes to a location your deployment probably does not copy.

The other shape of the answer

Every fix above keeps the renderer inside your deployment, which means the constraint stays yours: the package size, the shared libraries, the fonts, the memory, the patching. The alternative is an HTTP call from Google Cloud Run to something that already has a browser in it, which turns all of the above into a request and a response. That is what this API is, and it is the reason these pages exist. Whether that trade is right depends on whether rendering is a thing you want to operate.

Frequently asked

Can Playwright run on Google Cloud Run at all?

Install only the browser you use, on a slim base, rather than starting from the full image. Keep the dependency installer, because working out which system packages Chromium needs by hand is how you end up with an image that runs on your machine and not in the cluster. Whether that is worth doing is a separate question from whether it is possible, and the section above on what still hurts is the honest answer to it.

Why does it work locally?

Because a development machine has root, a package manager, several gigabytes of memory, a full font set and no packaging limit. Every one of those is a thing Google Cloud Run constrains. A renderer that works locally and fails on deploy has not regressed: it has met the first environment that does not give it everything.

Related failures

The same platform with a different renderer, and the same renderer on other platforms.

If the render moves off this platform, none of the above is your problem to keep solving. 100 free documents a month, and a playground that needs no signup.