Serverless functions / AWS Lambda
Playwright on AWS Lambda: browsers that are not in your bundle
At runtime, not at deploy time: "Executable doesn't exist at /home/sbx_user1051/.cache/ms-playwright/chromium-.../chrome-linux/chrome".
What is actually wrong
Playwright does not put its browsers in node_modules. It downloads them at install time into a cache directory in the user's home, which is deliberate and sensible for a development machine and wrong for a deployment artifact: whatever packages your function copies node_modules, and the browser is not in node_modules. The deploy succeeds, the size limit is never hit, and the failure arrives on the first invocation.
What AWS Lambda gives you to work with
a function that is handed a request, runs, and is frozen again. The execution environment is reused between invocations but you do not control when it is torn down.
- Packaging: 250 MB unzipped for the function code and every layer attached to it, combined. That is the hard number most PDF deployments run into. A container image raises it to 10 GB but changes how the function is built and pushed.
- Filesystem: the deployment package is read-only. Only /tmp is writable, and it defaults to 512 MB. Anything the process writes there survives for the life of the execution environment, not the invocation, which is a subtle source of both caching wins and stale-state bugs.
- Processes: a child process can be spawned and it lives while the invocation does. When the invocation returns, the environment is frozen mid-execution rather than terminated, so a process that has not exited is simply suspended and resumed on the next call.
The fix, if you are keeping Playwright
Set PLAYWRIGHT_BROWSERS_PATH to 0 before installing, which puts the browsers inside node_modules where the packaging step will find them. Then discover that the bundle is now well over the 250 MB unzipped limit, because Playwright installs three browser engines by default. Restrict the install to Chromium and it is still too large, which is why the realistic path on Lambda is a container image rather than a zip.
# Browsers into node_modules rather than the home cache.
PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium
# This is the point where the zip deployment stops being viable.
# A container image raises the ceiling to 10 GB:
# FROM public.ecr.aws/lambda/nodejs:20
# RUN npx playwright install --with-deps chromiumWhat still hurts after that
The container image path works, and it changes how the function is built, pushed and versioned: an image in a registry rather than a zip, a longer deploy cycle, and cold starts that pull a multi-gigabyte image. You have also taken on keeping a browser patched inside an image you now maintain, which is a security obligation rather than a build detail.
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 AWS Lambda 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 AWS Lambda at all?
Set PLAYWRIGHT_BROWSERS_PATH to 0 before installing, which puts the browsers inside node_modules where the packaging step will find them. Then discover that the bundle is now well over the 250 MB unzipped limit, because Playwright installs three browser engines by default. Restrict the install to Chromium and it is still too large, which is why the realistic path on Lambda is a container image rather than a zip. 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 AWS Lambda 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.
Puppeteer on AWS Lambda: the 250 MB package limit
The deploy fails before the function ever runs, with an error naming the unzipped size of the code and layers.
wkhtmltopdf on AWS Lambda: a binary with no libraries to link against
"error while loading shared libraries: libXrender.so.1: cannot open shared object file", or the process exits with no output and no error at all.
WeasyPrint on AWS Lambda: the libraries pip cannot install
"OSError: cannot load library 'libgobject-2.0-0': libgobject-2.0-0: cannot open shared object file", raised on import rather than on render.
Playwright on Vercel: an install step the build never runs
Deploy succeeds, first request fails with "Executable doesn't exist at ...ms-playwright/chromium-...".
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.
Playwright on Docker: browsers downloaded outside the image layer
The image builds, and at runtime the browser is missing, or the container downloads it again on every start.
Playwright on Kubernetes: an image that slows every scale-up
Autoscaling responds to load and the new pods take a long time to become ready, so the spike is over before the capacity arrives.
Every renderer, on every platform
The full grid, grouped by what kind of environment the platform is.
What goes wrong when HTML becomes a PDF
Once it deploys, the next set of problems is in the output rather than the infrastructure.
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.