Skip to main content

Command Palette

Search for a command to run...

How to get WebGPU in Headless Chrome on Cloud GPUs

Updated
โ€ข5 min read
How to get WebGPU in Headless Chrome on Cloud GPUs
T

Just a guy who loves to write code and watch anime.

Introduction

I got WebGPU working in headless Chrome with a real NVIDIA GPU in a container (no display).

Tested and verified on RunPod (A40, driver 570), Google Colab (T4, driver 580), and Modal (T4, driver 580).

Struggled on this for about ~6 hours. Had to fix bunch of undocumented issues. ๐Ÿซฉ

Results

PlatformGPUDriverAdapter FoundCompute Shader
RunPodA405703/3 consistentnot tested (SSH corruption)
ColabT45803/3 consistent[1,2,3,4,5,6,7,8] โ†’ [2,4,6,8,10,12,14,16] with 18 features

The 4 Problems and Their Fixes

Problem 1: Vulkan loader too old

Symptom: vulkaninfo shows ERROR_INCOMPATIBLE_DRIVER or Could not get vkCreateInstance.

Cause: Ubuntu 22.04 ships Vulkan loader 1.3.204. NVIDIA drivers 570+ declare Vulkan 1.4. The old loader rejects the newer driver entirely.

Fix:

wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | apt-key add -
wget -qO /etc/apt/sources.list.d/lunarg-vulkan.list \
  https://packages.lunarg.com/vulkan/lunarg-vulkan-jammy.list
apt update -qq && apt install -qqq libvulkan1

Verify: vulkaninfo --summary should show your GPU (e.g. Tesla T4 or NVIDIA A40) with Vulkan 1.4.x.

Problem 2: Missing NVIDIA Vulkan ICD

Symptom: vulkaninfo says no vulkan icd or no GPU devices found.

Cause: The container has libnvidia-gl-525 installed but the actual driver is 570/580. The ICD package must match the running driver version.

Fix:

# Check your driver version first
nvidia-smi | head -3

# Install matching package (replace 580 with your version)
apt install -qqq libnvidia-gl-580

Note: If this fails with Invalid cross-device link (RunPod), the driver libs are host-mounted and can't be replaced. In that case the ICD may already exist at /etc/vulkan/icd.d/nvidia_icd.json โ€” check with cat /etc/vulkan/icd.d/*.json.

Problem 3: navigator.gpu is undefined

Symptom: navigator.gpu returns undefined in page.evaluate().

Cause: Two things:

  1. WebGPU requires a secure context. about:blank (Puppeteer's default page) is NOT a secure context. http://localhost IS.

  2. Puppeteer silently injects --use-angle=swiftshader-webgl which overrides your --use-angle=vulkan.

Fix:

// 1. Start a localhost server
const http = require("http");
const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end("ok");
});
server.listen(8080, "127.0.0.1");

// 2. Override Puppeteer's default ANGLE flag
const browser = await puppeteer.launch({
  headless: "new",
  ignoreDefaultArgs: ["--use-angle=swiftshader-webgl"],
  args: [
    /* your flags */
  ],
});

// 3. Navigate to localhost BEFORE checking navigator.gpu
const page = await browser.newPage();
await page.goto("http://127.0.0.1:8080");

Also start dbus before launching Chrome:

/etc/init.d/dbus start

Problem 4: requestAdapter() returns null

Symptom: navigator.gpu exists, but requestAdapter() returns null.

Cause: Chrome's WebGPU engine (Dawn) has its own GPU blocklist separate from Chrome's --ignore-gpu-blocklist flag. Dawn rejects NVIDIA drivers 570+ by default.

Fix: Disable Dawn's adapter blocklist:

--enable-dawn-features=allow_unsafe_apis,disable_adapter_blocklist
--disable-dawn-features=disallow_unsafe_apis

Also use powerPreference: 'low-power' in the adapter request (the default preference returns null on some setups):

const adapter = await navigator.gpu.requestAdapter({
  powerPreference: "low-power",
});

The Complete Working Setup

System setup (run once)

# 1. Install Chrome
apt update && apt install -y wget
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome-stable_current_amd64.deb

# 2. Install matching NVIDIA GL/Vulkan ICD (replace version as needed)
apt install -qqq libnvidia-gl-580  # or 570, match nvidia-smi output

# 3. Upgrade Vulkan loader to 1.4+
wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | apt-key add -
wget -qO /etc/apt/sources.list.d/lunarg-vulkan.list \
  https://packages.lunarg.com/vulkan/lunarg-vulkan-jammy.list
apt update -qq && apt install -qqq libvulkan1

# 4. Start dbus
/etc/init.d/dbus start

# 5. Verify Vulkan sees your GPU
vulkaninfo --summary 2>&1 | grep -A3 "GPU0"

# 6. Install Node + Puppeteer
npm install puppeteer-core

JavaScript (the exact code that works)

import puppeteer from "puppeteer-core";
import { createServer } from "http";

// Localhost server for secure context
const server = createServer((req, res) => {
  res.writeHead(200);
  res.end("ok");
});
await new Promise((r) => server.listen(8080, "127.0.0.1", r));

const browser = await puppeteer.launch({
  executablePath: "/usr/bin/google-chrome-stable",
  headless: "new",
  ignoreDefaultArgs: ["--use-angle=swiftshader-webgl"],
  args: [
    "--no-sandbox",
    "--headless=new",
    "--enable-unsafe-webgpu",
    "--enable-features=Vulkan",
    "--use-angle=vulkan",
    "--disable-vulkan-surface",
    "--ignore-gpu-blocklist",
    "--disable-gpu-sandbox",
    "--enable-dawn-features=allow_unsafe_apis,disable_adapter_blocklist",
    "--disable-dawn-features=disallow_unsafe_apis",
  ],
});

const page = await browser.newPage();
await page.goto("http://127.0.0.1:8080");

const result = await page.evaluate(async () => {
  if (!navigator.gpu) return "no gpu";
  const adapter = await navigator.gpu.requestAdapter({
    powerPreference: "low-power",
  });
  if (!adapter) return "no adapter";
  return (
    "FOUND: features=" +
    adapter.features.size +
    " fallback=" +
    adapter.isFallbackAdapter
  );
});

console.log(result);
await browser.close();
server.close();

Chrome Flags Explained

FlagWhy
--no-sandboxRequired in containers (running as root)
--headless=newNew headless mode (not legacy)
--enable-unsafe-webgpuEnable WebGPU on Linux
--enable-features=VulkanEnable Vulkan graphics backend
--use-angle=vulkanTell ANGLE to use Vulkan
--disable-vulkan-surfaceDisable swapchain (no display needed, offscreen only)
--ignore-gpu-blocklistIgnore Chrome's GPU blocklist
--disable-gpu-sandboxDisable GPU process sandbox in container
--enable-dawn-features=allow_unsafe_apis,disable_adapter_blocklistTHE KEY FLAG. Disables Dawn's own separate blocklist that rejects newer NVIDIA drivers
--disable-dawn-features=disallow_unsafe_apisCompanion to above

Puppeteer Gotchas

  1. ignoreDefaultArgs: ['--use-angle=swiftshader-webgl'] โ€” Puppeteer silently injects this flag which forces SwiftShader and overrides your Vulkan flag. You MUST exclude it.

  2. Navigate to http://localhost before checking navigator.gpu โ€” WebGPU requires a secure context. about:blank is not one. http://localhost is.

  3. Use powerPreference: 'low-power' in requestAdapter() โ€” The default preference returns null on some setups.

  4. Start dbus โ€” Chrome expects dbus to be running. /etc/init.d/dbus start before launching.

The Stack

Your Three.js code
  โ†’ WebGPU API (navigator.gpu)
    โ†’ Chrome's Dawn engine
      โ†’ Vulkan
        โ†’ NVIDIA driver
          โ†’ GPU hardware

What Does NOT Work

  • Browserbase โ€” No GPU. Intentionally blocked. SwiftShader only.

  • Browserless self-serve โ€” No GPU on standard plans. SwiftShader only.

  • AWS Lambda โ€” No GPU available.

  • Vercel serverless โ€” No GPU available.

  • Any container with mismatched Vulkan loader + NVIDIA driver versions โ€” Loader must support the Vulkan version the driver declares.

  • Puppeteer default flags without ignoreDefaultArgs โ€” SwiftShader override kills Vulkan.

  • about:blank or file:// URLs โ€” Not secure contexts. WebGPU won't initialize.