Managed containers / Fly.io
Playwright on Fly.io: image size is boot time
With machines stopping when idle, the first request after a quiet period waits several seconds before any application code runs.
What is actually wrong
A stopped machine has to be started, and starting it means having the image. An image carrying three browser engines is a large thing to move and unpack, and that time lands on whichever request happens to arrive first. Nothing is broken, which is why it gets misdiagnosed as a slow endpoint.
What Fly.io gives you to work with
your image running in a lightweight virtual machine, one per instance, started and stopped on demand.
- Packaging: no image size limit, but image size is boot time, and boot time is what the scale-to-zero behaviour trades against.
- Filesystem: fully writable inside the machine, ephemeral unless a volume is attached. /dev/shm inside the virtual machine is sized by the machine, not by a Docker default.
- Processes: full control. The default machine size is small, and memory is the resource that a browser exhausts first.
The fix, if you are keeping Playwright
Build a single-browser image on a slim base instead of using the full official image, which typically removes most of the size. If the latency still matters, keep one machine running rather than allowing all of them to stop.
What still hurts after that
Keeping a machine running removes the cold start and the scale-to-zero saving together. A smaller image helps and does not eliminate the gap, because a browser launch is itself a second or two. If the first render after an idle period has to be fast, the browser cannot be started on the request path at all.
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 Fly.io 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 Fly.io at all?
Build a single-browser image on a slim base instead of using the full official image, which typically removes most of the size. If the latency still matters, keep one machine running rather than allowing all of them to stop. 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 Fly.io 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 Fly.io: a machine too small to hold a browser
The machine is killed and restarted mid-render, with an out-of-memory entry in the logs and no application error.
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".
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.
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.