Reduce slug size when using Puppeteer with Heroku


Skip to fix: How to reduce your slug size

When using Puppeteer with Heroku, you may encounter the following error during deployment:

Failed to launch the browser process! /usr/src/app/node_modules/puppeteer/.local-chromium/linux-756035/chrome-linux/chrome: error while loading shared libraries: libX11-xcb.so.1: cannot open shared object file: No such file or directory TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md

This happens because Heroku’s Linux image does not come pre-built with the dependencies required to run Heroku.

To fix this, Puppeteer’s docs suggests adding the following buildpack:

https://github.com/jontewks/puppeteer-heroku-buildpack

along with several arguments to the launch process:

puppeteer.launch({ args: ['--no-sandbox'] })

This works, but you’re likely to encounter the following warning when deploying with Heroku:

Warning: Your slug size (368 MB) exceeds our soft limit (300 MB) which may affect boot time.

Jon’s buildpacks work flawlessly. But, it adds a lot of memory overhead to the Heroku slug which can cause issues if it goes over Heroku’s hard limit (500MB). It’s also recommended to keep your slugs small as per Heroku’s docs:

Smaller slugs can be transferred to the dyno manager more quickly, allowing for more immediate scaling. You should try to keep your slugs as small and nimble as possible.

Here’s how to reduce your slug size:

Solution

1. Remove the https://github.com/jontewks/puppeteer-heroku-buildpack buildpack.

heroku buildpacks:remove jontewks/puppeteer -a <YOUR_APP_NAME>

Sorry, Jon!

2. Add the heroku/google-chrome buildpack.

The heroku/google-chrome buildpack installs a headless version of Google Chrome in your dyno. We will be using this later to launch Puppeteer.

heroku buildpacks:add heroku/google-chrome --index=1 -a <YOUR_APP_NAME>

It doesn’t matter which index you add the buildpack in as long as it’s not the last buildpack. You want to keep the last buildpack to determine the process type for your application, which you can read more here.

3. Set a few Heroku environment variables

Puppeteer’s installation also installs Chromium by default, which we don’t want since we’re using Google Chrome buildpack from before.

We can set a couple of environment variables to prevent this:

heroku config:set PUPPETEER_SKIP_DOWNLOAD=true -a <YOUR_APP_NAME>
heroku config:set PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true -a <YOUR_APP_NAME>

Here is the link to the code for the second option (PUPPETEER_SKIP_CHROMIUM_DOWNLOAD).

4. Update Puppeteer’s launch options to use Google Chrome

When initializing or calling Puppeteer, pass in the no-sandbox option to the executablePath property in puppeteer.launch constructor, like so:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  executablePath: "google-chrome",
  args: ["--no-sandbox"]
})

This fixes the following error when using a non-sandboxed Google Chrome instance in Heroku:

No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux_suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using --no-sandbox

If you’re using the grover gem for your Ruby on Rails app (like me), you can pass the options like so to grover:

Grover.configure do |config|
  config.options = {
    ...
    executable_path: "google-chrome-stable"
  }
end

This should work the same for any other Ruby gems.

5. (Optional) Set more launch options to optimize your Puppeteer performance.

I have the following launch options set in grover for my app. You can read more on what each option does here.

Grover.configure do |config|
  config.options = { 
    launch_args: [
      "--disable-background-networking",
      "--disable-background-timer-throttling",
      "--disable-backgrounding-occluded-windows",
      "--disable-breakpad",
      "--disable-component-update",
      "--disable-default-apps",
      "--disable-dev-shm-usage",
      "--disable-domain-reliability",
      "--disable-extensions",
      "--disable-features=AudioServiceOutOfProcess",
      "--disable-hang-monitor",
      "--disable-ipc-flooding-protection",
      "--disable-notifications",
      "--disable-popup-blocking",
      "--disable-print-preview",
      "--disable-prompt-on-repost",
      "--disable-renderer-backgrounding",
      "--disable-setuid-sandbox",
      "--disable-speech-api",
      "--hide-scrollbars",
      "--ignore-gpu-blacklist",
      "--metrics-recording-only",
      "--mute-audio",
      "--no-default-browser-check",
      "--no-first-run",
      "--no-pings",
      "--no-sandbox",
      "--no-zygote",
      "--password-store=basic",
      "--use-gl=swiftshader"
    ]   
  }
end

6. (Optional) Clear your build cache

It also helps to clear your Heroku build cache if you haven’t in awhile. This removes any old caches or artifacts that is no longer used by your app during deployment. Heroku explains how to do this here.

Your slug size should have reduced dramatically with the steps above. Enjoy!