It will not run
Renders crash under concurrency in a container
Single renders work. Two or three at once crash, with a tab-closed error or a plain crash. It never reproduces locally, where there is no container.
What is actually happening
Docker gives a container 64MB of shared memory by default, and a browser uses shared memory heavily for its renderer processes. One tab fits. Several do not, and the failure looks like a crash rather than like a resource limit.
Confirming it is this and not something that looks like it
Raise the shared memory size and try the same concurrency. If it stops crashing, that was it, and no amount of code change would have found it.
The fix
Raise the shared memory allocation for the container, or disable the browser's use of it, which trades some performance for not crashing. Both are container-level changes rather than application changes, which is why this one is hard to find from inside the application.
# 64MB is the default and it is not enough for concurrent renders.
docker run --shm-size=1g my-app
# Or in compose:
# services:
# app:
# shm_size: 1gbIf that was not it
These produce the same symptom often enough to be worth ruling out before assuming the fix above did not work.
- --disable-dev-shm-usage as the alternative, which moves the scratch space to disk
- Kubernetes, where the fix is an emptyDir volume with medium: Memory mounted at /dev/shm
- Memory limits that make the shared memory increase impossible to satisfy
- Concurrency that only reaches the threshold in production, which is why it passes CI
Frequently asked
Does this happen with every rendering engine?
The behaviour behind it is not specific to one tool. Docker gives a container 64MB of shared memory by default, and a browser uses shared memory heavily for its renderer processes. Anything rendering HTML to a paged medium has to make the same decision, so the fix travels with you if you change how the render happens.
Will this show up as an error in my logs?
Yes, this one surfaces as a thrown error or a failed status, which is why it is at least findable. The harder half is that the message often names the call that was in flight rather than the thing that actually failed.
Related failures
Problems people arrive at from the same starting point, or mistake for this one.
Failed to launch the browser process
A headless browser is not a self-contained binary.
libnss3.so: cannot open shared object file
This is the previous error with the first missing library named.
Navigation timeout of 30000 ms exceeded
The default wait condition is the network going idle, and something on the page is keeping it busy: an analytics beacon, a font request to a host that is not responding, a polling request, or an ad script.
Protocol error (Page.printToPDF): Target closed
The tab crashed, and by far the most common reason is memory.
Chromium does not fit in an AWS Lambda deployment package
A full browser build is several hundred megabytes and the unzipped package limit is 250MB.
CSS for paged documents, and what actually works
Which parts of the paged media specification a browser-based render implements.
Everything that goes wrong, by category
The full list, grouped by where in the pipeline it breaks.
Paste your markup and see the rendered document, without signing up.