Skip to content
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 the same way it does on a laptop. Each sandbox is a Firecracker microVM with Node.js 24, sudo and its own kernel. 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. With both CPUs rendering, it costs $0.08 an hour (pricing). Puppeteer 25.16.0 was the current npm release on 25 October 2026.

Render a page to PDF

The script runs inside the sandbox. Your code on the host sends the script 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.16.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.16.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. In a sandbox, that path is /workspace/.cache/puppeteer. Puppeteer's installation guide puts the Linux download at about 282 MB. The sample therefore 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 that Chrome needs. Several of them 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 packages. So if you copy the list unchanged, apt-get stops. These are the noble names, each checked in the Ubuntu archive's noble package index on 25 October 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 packages of the tree and lets apt install 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. It needs root, which recipe commands have. PUPPETEER_CACHE_DIR moves the browser out of root's home directory, so the sandbox user can find 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.16.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.16.0",        "npx puppeteer browsers install chrome --install-deps",    ],})sbx = runtime.sandboxes.create(image="puppeteer")

Building is free. Without credit, the build counts toward its hours. A stored image is charged by its size. An account without credit stores three images free.

Keep a scraper to the sites it should visit

A scraper that follows links chosen by an LLM 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 off the web 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; without credit, up to 10 GiB
A site on port 8443 never answers Sandboxes without credit 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. It suits 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 free. A new account gets 100 free hours of a 1 GB machine in its first 30 days, with no card:

Terminalnpx withruntime sandboxes run -- node --version

Sources

Facts on this page were checked on 25 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card