Jason Sultana

Follow this space for writings (and ramblings) about interesting things related to software development.

Running Puppeteer under Docker

15 Jan 2022 » docker

G’day guys!

I recently tried to dockerise an old hobby project and unsurprisingly, a couple of things broke. Some of these were fairly simple fixes so I won’t go into their details - but I will go into a fairly obscure one, which was caused by puppeteer. Regarding the application itself, it does a few things, but for the purposes of this article, let’s just say that it renders some reports using Puppeteer and NodeJS (while running as a .NET app).

Dance monkey, dance monkey, dance…wait, what?

If you haven’t heard of Puppeteer, it’s basically a NodeJS library that allows you to run (and control) an instance of headless chrome. i.e - an instance of Google chrome without a UI. In this project, I was using it to render PDF exports of reports. Running natively, it worked like a charm. But under docker, well…I’m writing this article after all, aren’t I?

Hold up, let’s see your docker setup first.

I thought you might say that! Well, first here’s the dockerfile.

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
COPY ["MyApplication.API/MyApplication.API.csproj", "MyApplication.API/"]
COPY ["MyApplication.Common/MyApplication.Common.csproj", "MyApplication.Common/"]
COPY ["MyApplication.Data/MyApplication.Data.csproj", "MyApplication.Data/"]
COPY ["MyApplication.Logic/MyApplication.Logic.csproj", "MyApplication.Logic/"]
RUN dotnet restore "MyApplication.API/MyApplication.API.csproj"
COPY . .
WORKDIR "/src/MyApplication.API"
RUN dotnet build "MyApplication.API.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "MyApplication.API.csproj" -c Release -o /app/publish

FROM base AS final
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApplication.API.dll", "--environment", "Docker"]

# Install node and npm
RUN apt-get update
RUN apt-get install nodejs=12.22.5~dfsg-2~11u1 -y
RUN apt-get install npm=7.5.2+ds-2 -y
ENV NODE_ENV=production

# Install node modules for reports
WORKDIR "/app/Reports/jsreport"
RUN npm install

# Set the directory back so dotnet is able to run the application
WORKDIR "/app"

Basically I load the ASP.NET 6 base image, build the solution, publish it, install require node modules and set the dotnet program as the entry point of the container.

Then I build an image using docker build -t myimage -f ./MyApplication.API/Dockerfile .

Note that in order for the build step in the docker file to work, the image has to be created at the solution level and not the project level - since all of the projects need to be built. Hence why I’m using the solution directory when we build the image.

Then I run a container using docker run -p myimage.

So far so good! But after invoking one of the reports yields an internal server error.

Show me the errors!

Well, since this is running under docker (and this is just a hobby app, so I’m using old-school rolling file logs), I actually need to sh into the container first to see the logs. So first to get the id of the container: docker ps, and once we have the id of the container: docker exec -it CONTAINER_ID /bin/bash. Opening the error log reveals:

  Jering.Javascript.NodeJS.InvocationException: Failed to launch chrome!
/app/Reports/jsreport/node_modules/puppeteer/.local-chromium/linux-609904/chrome-linux/chrome: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory

TROUBLESHOOTING: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md

Error: Failed to launch chrome!

Interesting! Following the link provided and scrolling down to Running Puppeteer under Docker tells you that we basically need to add a step in our dockerfile to install the necessary libraries (particularly libnss3.so) in order for headless chrome to run. The dockerfile in their example is a bit verbose with some unnecessary steps, but the real magic is basically just:

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

And voila! Puppeteer is now working under Docker. Hope this helped someone out, or was at least an interesting read :)

Catch ya!