How to run Puppeteer in a cloud sandbox
Install puppeteer from npm in an Ubuntu 24.04 microVM, add Chrome's system libraries with apt, run the script, and read the PDF back.
On Runtime, Puppeteer's own download of Chrome for Testing works as it does
on a laptop. Each sandbox is a Firecracker microVM with Node.js 24, sudo
and its own kernel, so a page that misbehaves stays inside a machine that
exists only for that job. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while
the browser waits on the network and $0.08 an hour with both CPUs rendering
(pricing). Puppeteer 25.12.0 was the current npm release on
25 September 2026.
Render a page to PDF
The script runs inside the sandbox; your code on the host sends it in and reads the PDF out.
TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";const chromeLibs = "fonts-liberation libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libcups2t64 " + "libgbm1 libglib2.0-0t64 libgtk-3-0t64 libnspr4 libnss3 libpango-1.0-0 libxcomposite1 " + "libxdamage1 libxfixes3 libxkbcommon0 libxrandr2 libxss1";const script = `import puppeteer from "puppeteer";const browser = await puppeteer.launch();const page = await browser.newPage();await page.goto(process.argv[2], { waitUntil: "networkidle0" });await page.pdf({ path: "page.pdf", format: "A4", printBackground: true });console.log(await page.title());await browser.close();`;await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 900 });const slow = { check: true, timeoutMs: 600_000 } as const;await sbx.exec(`sudo apt-get update -qq && sudo apt-get install -y -qq ${chromeLibs}`, slow);await sbx.exec("npm install --no-fund --no-audit puppeteer@25.12.0", slow);await sbx.files.write("/workspace/render.mjs", script);const run = await sbx.exec(["node", "render.mjs", "https://example.com"], { check: true });console.log(run.stdout);await writeFile("page.pdf", await sbx.files.read("/workspace/page.pdf"));Pythonfrom withruntime import SandboxCHROME_LIBS = ( "fonts-liberation libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libcups2t64 " "libgbm1 libglib2.0-0t64 libgtk-3-0t64 libnspr4 libnss3 libpango-1.0-0 libxcomposite1 " "libxdamage1 libxfixes3 libxkbcommon0 libxrandr2 libxss1")with open("render.mjs") as handle: # the same Puppeteer script as above script = handle.read()with Sandbox.create(disk_mib=8192, timeout_seconds=900) as sbx: sbx.exec(f"sudo apt-get update -qq && sudo apt-get install -y -qq {CHROME_LIBS}", check=True, timeout_ms=600_000) sbx.exec("npm install --no-fund --no-audit puppeteer@25.12.0", check=True, timeout_ms=600_000) sbx.files.write("/workspace/render.mjs", script) run = sbx.exec(["node", "render.mjs", "https://example.com"], check=True) print(run.stdout) sbx.files.download("/workspace/page.pdf", "page.pdf")npm install puppeteer downloads Chrome for Testing and chrome-headless-shell
into ~/.cache/puppeteer, which in a sandbox is /workspace/.cache/puppeteer.
Puppeteer's installation guide puts the Linux download at about 282 MB, so the
sample asks for an 8 GiB disk: the default 4 GiB sandbox had about 2.5 GiB free
on 24 September 2026.
Chrome's libraries on Ubuntu 24.04
Puppeteer's troubleshooting guide lists the Debian packages Chrome needs, but
several were renamed in Ubuntu 24.04 when the archive moved to 64-bit time
(t64). The old names either no longer exist on noble or are virtual, so
copying the list unchanged makes apt-get stop. These are the noble names,
each checked on packages.ubuntu.com on 25 September 2026:
| Puppeteer's list says | Install on Ubuntu 24.04 |
|---|---|
libasound2 |
libasound2t64 |
libatk-bridge2.0-0 |
libatk-bridge2.0-0t64 |
libatk1.0-0 |
libatk1.0-0t64 |
libcups2 |
libcups2t64 |
libglib2.0-0 |
libglib2.0-0t64 |
libgtk-3-0 |
libgtk-3-0t64 |
libnss3, libgbm1 |
unchanged |
The sample names the top of the tree and lets apt bring in their
dependencies. X11 and D-Bus libraries such as libx11-6, libxtst6 and
libdbus-1-3 keep their old names on noble.
Start every sandbox with Chrome installed
A custom image runs the install once. Puppeteer's own
--install-deps flag installs Chrome's system libraries on Ubuntu and Debian
and needs root, which recipe commands have. PUPPETEER_CACHE_DIR moves the
browser out of root's home so the sandbox user finds it.
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({ name: "puppeteer", recipe: { env: { PUPPETEER_CACHE_DIR: "/opt/puppeteer" }, commands: [ "npm init -y && npm install --no-fund --no-audit puppeteer@25.12.0", "npx puppeteer browsers install chrome --install-deps", ], },});await using sbx = await runtime.sandboxes.create({ image: "puppeteer" });Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="puppeteer", recipe={ "env": {"PUPPETEER_CACHE_DIR": "/opt/puppeteer"}, "commands": [ "npm init -y && npm install --no-fund --no-audit puppeteer@25.12.0", "npx puppeteer browsers install chrome --install-deps", ],})sbx = runtime.sandboxes.create(image="puppeteer")Building is free and uses no trial hours; a stored image is charged on its size, and the free trial stores three images free.
Keep a scraper to the sites it should visit
A scraper that follows links an LLM chose can be held to a list. Network rules are enforced on the host, so root inside the sandbox cannot lift them:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ image: "puppeteer", network: { internet: true, allow: ["example.com", "*.example.com"] },});Anything off the list is refused, on every port. internet: false turns the
web off entirely once the pages are loaded
(turn off sandbox internet).
Things that trip people up
| Symptom | What is going on |
|---|---|
error while loading shared libraries |
A library from the table above is missing; install the noble name |
No usable sandbox! |
Puppeteer's guide says Ubuntu 23.10 and later ship an AppArmor profile that stops Chrome for Testing using user namespaces; it recommends configuring a sandbox rather than --no-sandbox |
| Command timed out after 60 seconds | The default exec timeout; give installs timeoutMs |
ENOSPC during npm install |
Ask for diskMiB: 8192; the trial allows up to 10 GiB |
| A site on port 8443 never answers | Trial sandboxes reach ports 80 and 443; paid sandboxes reach any port |
Puppeteer, Playwright or the desktop
- Puppeteer suits code already written against it and Chrome-only jobs such as PDF rendering.
- Playwright drives Chromium, Firefox and WebKit and brings its own test runner.
- The sandbox desktop takes clicks and keys and returns screenshots, for an agent that looks at the screen instead of the DOM (browser automation agent).
- No framework at all: Chrome's own command line prints PDFs and screenshots (headless Chrome).
Try it on the free trial, 50 sandbox hours with no card:
Terminalnpx withruntime sandbox run --trial -- node --versionSources
- Puppeteer installation guide, https://pptr.dev/guides/installation, read 25 September 2026
- Puppeteer troubleshooting, https://pptr.dev/troubleshooting, read 25 September 2026
- Puppeteer configuration, https://pptr.dev/guides/configuration, read 25 September 2026
- Puppeteer browsers CLI, https://pptr.dev/browsers-api, read 25 September 2026
- npm registry, https://registry.npmjs.org/puppeteer, read 25 September 2026
- Ubuntu 24.04 package pages, https://packages.ubuntu.com/noble/, read 25 September 2026
Facts on this page were checked on 25 September 2026.