Runtime

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 --version

Sources

Facts on this page were checked on 25 September 2026.