# 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](/docs/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. ```ts check import { 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")); ``` ```python check from withruntime import Sandbox CHROME_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](/docs/images) 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. ```ts check import { 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" }); ``` ```python check from withruntime import Runtime runtime = 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: ```ts check import { 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](/how-to/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](/integrations/playwright)** drives Chromium, Firefox and WebKit and brings its own test runner. - **The sandbox [desktop](/docs/javascript#a-desktop)** takes clicks and keys and returns screenshots, for an agent that looks at the screen instead of the DOM ([browser automation agent](/use-cases/browser-automation-agent)). - **No framework at all:** Chrome's own command line prints PDFs and screenshots ([headless Chrome](/integrations/headless-chrome)). Try it on the free trial, 50 sandbox hours with no card: ```bash no-run npx withruntime sandbox run --trial -- node --version ``` ## Sources - 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.