Running Puppeteer in a Docker container on Raspberry Pi

Puppeteer is a Node.js module that allows interacting with a (headless) web browser programmatically. This is extremely useful for automating website testing, generating screenshots and PDFs of web pages or programmatic form submission.

Docker offers numerous benefits including a standardized environment, isolation, and rapid deployment to name a few. These benefits might be why you’d want to run Puppeteer inside a Docker container. Unfortunately, doing so on Raspberry Pi is not straightforward. There are a few issues that make it harder than usual. Luckily, they are all solvable. Let’s take a look.

Problem 1: Chromium included in Puppeteer does not work on Raspberry Pi

Puppeteer by default downloads a matching version of Chromium which is guaranteed to work out of the box on supported platforms. Unfortunately, Chromium does not currently provide an arm build that works on Raspberry Pi and running stock Puppeteer on Raspberry Pi will end up with a crash. This can be solved by installing Chromium with apt-get install chromium -y and telling Puppeteer to use it by passing  the executablePath: '/usr/bin/chromium' to the launch() function as follows:

   const browser = await puppeteer.launch({
        executablePath: '/usr/bin/chromium',
        args: []

When doing this, Puppeteer no longer should need to download Chromium as it will be using the version installed with apt-get, so it makes sense to skipping this step by setting the corresponding environment variable in the Dockerfile:


This will significantly reduce the time needed to install node modules.

Problem 2: The base image installs an old version of Chromium

Some node Docker images are based on old distributions that contain only older versions of Chromium. Most notably the node:16 image is based on buster. If you use this base image the only version of Chromium you will be able to install with apt-get is 90.0.4430.212-1. Unfortunately this version doesn’t work in a Docker container – it just hangs indefinitely. Moving to the node:16-bullseye base image allows installing a much newer version of Chromium (108.0.5359.124) where this is no longer a problem.

Problem 3: Puppeteer crashes on launch

Puppeteer will not launch in a Docker container without additional configuration. Chromium is not able to provide sandboxing when running inside a container so it needs to be launched at least with the --no-sandbox argument. Otherwise it will crash with the following error message

Failed to move to new namespace: PID namespaces supported, Network namespace 
 supported, but failed: errno = Operation not permitted

Sandbox is a security feature and running without a sandbox is generally discouraged. Unfortunately, running without a sandbox appears to be currently the only way to run Puppeteer inside a Docker container. In the past the --no-sandbox option required running Puppeteer as root, only increasing the risk. Luckily, this no longer seems to be the case – it is possible now to launch puppeteer with the --no-sandbox option as a non-privileged user.

There are a few more options that might be worth exploring if launching Puppeteer inside a container fails:

  • --disable-gpu – disables GPU hardware acceleration (which is usually not available when running in Docker)
  • --disable-dev-shm-usage – prevents from using shared RAM (/dev/shm/)
  • --disable-setuid-sandbox – disabled setui sandbox

Putting everything together

The information provided above should be all that is needed to be build a Docker image for a Node.js app that uses Puppeteer and runs on Raspberry Pi. Below is an example Dockerfile for such a Docker image. It contains comments to make it easy to notice how the solutions discussed above were applied.

# Ensure an up-to-date version of Chromium 
# can be installed (solves Problem 2)
FROM node:16-bullseye 
# Install a working version of Chromium (solves Problem 1)
RUN apt-get update
RUN apt-get install chromium -y
ENV HOME=/home/app-user
RUN useradd -m -d $HOME -s /bin/bash app-user 
RUN mkdir -p $HOME/app 
COPY package*.json ./
COPY index.js ./
RUN chown -R app-user:app-user $HOME
# Run the container as a non-privileged user (discussed in Problem 3)
USER app-user
# Make `npm install` faster by skipping 
# downloading default Chromium (discussed in Problem 1)
RUN npm install
CMD [ "node", "index.js" ]

Because the application also requires a couple modifications to how the headless browser is launched here is a small example application illustrating these changes with comments:

const puppeteer = require('puppeteer');
(async() => {
    const browser = await puppeteer.launch({
        // use Chromium installed with `apt` (solves Problem 1)
        executablePath: '/usr/bin/chromium',
        args: [
            // run without sandbox (solves Problem 3)
            // other launch flags (discussed in Problem 3)
            // '--disable-gpu,
            // '--disable-dev-shm-usage',
            // '--disable-setuid-sandbox',
    const page = await browser.newPage();
    await page.goto('', {waitUntil: 'networkidle2'});
    let e = await page.$('div#hplogo');
    let p = await e?.getProperty('title');
    if (p) {
      console.log(`Today's doodle: ${await p.jsonValue()}`);
    } else {
      console.log('No Doodle today :(');

Finally, here is the output of this application when run in a container:

Both the application and the Dockerfile are also available on Github


Running Puppeteer inside a Docker container is tricky – especially, when doing so on Raspberry Pi. The post discussed the key obstacles and provided solutions to overcome them. In addition, a demo containerized app was included to illustrate the main points.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: