I have an instance of puppeteer-sharp running on ASP.NET Core 3.1 that works fine locally. For deployment, it gets built in a docker container and deployed to a K8 dev cluster. The docker container builds fine, but the app fails at runtime with the error Failed to launch Base! /app/.local-chromium/Linux-706915/chrome-linux/chrome: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory
when trying to use puppeteer-sharp.
From the following resources, I pieced together the below dockerfile. I'm still fairly inexperienced with docker so imagine it's something wrong with the config, but I'm open to suggestions!
Resources used:
Dockerfile:
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
#####################
#PUPPETEER RECIPE
#####################
# 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.
ARG CHROME_VERSION="81.0.4044.138-1"
RUN apt-get update && apt-get -f install && apt-get -y install apt-utils wget gnupg2
RUN apt-get -f install && apt-get -y install gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils
RUN wget --no-verbose -O /tmp/chrome.deb http://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& apt-get update \
&& apt-get install -y /tmp/chrome.deb --no-install-recommends --allow-downgrades \
&& rm /tmp/chrome.deb
# NOTE: REMOVED THIS WHILST DEBUGGING - D
# Add user, so we don't need --no-sandbox.
# same layer as npm install to keep re-chowned files from using up several hundred MBs more space
#RUN groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
#&& mkdir -p /home/pptruser/Downloads \
#&& chown -R pptruser:pptruser /home/pptruser
#
## Run everything after as non-privileged user.
#
#USER pptruser
ENV PUPPETEER_EXECUTABLE_PATH "/usr/bin/google-chrome-unstable"
#####################
#END PUPPETEER RECIPE
#####################
COPY ["ProjectName.Core/ProjectName.Core.csproj", "ProjectName.Core/"]
RUN dotnet restore "ProjectName.Core/ProjectName.Core.csproj"
COPY . .
WORKDIR "/src/ProjectName.Core"
RUN dotnet build "ProjectName.Core.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "ProjectName.Core.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProjectName.Core.dll"]
Code Implementation:
public static async Task<Stream> GeneratePdfFromHtml(string html, List<string> styleUrls = null)
{
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true
});
using (var page = await browser.NewPageAsync())
{
await page.SetContentAsync(html);
if(styleUrls != null)
foreach (var url in styleUrls)
{
await page.AddStyleTagAsync(url);
}
return await page.PdfStreamAsync();
}
}
The resources using Docker all target .NET Core 2.0 so I don't know if that has anything to do with it. The fact that it runs fine outside of a Docker environment suggests the .NET Core version isn't the problem and is more likely a step in the dockerfile itself.
Ok, turns out it was a mix of things. The dockerfile needed rearranging and works best without a sandbox. As you'll see, the original code was installing dependencies on the intermediate image rather then the final/base.
For some reason, I couldn't get it to work under a sandboxed user properly, so that was removed. As a result the implementation needed updating to enable the --no-sandbox argument.
Here's the updated code for anyone else struggling to get puppeteer-sharp working with .NET Core 3.1.
dockerfile
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
#####################
#PUPPETEER RECIPE
#####################
# 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.
ARG CHROME_VERSION="81.0.4044.138-1"
RUN apt-get update && apt-get -f install && apt-get -y install wget gnupg2 apt-utils
RUN wget --no-verbose -O /tmp/chrome.deb http://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& apt-get update \
&& apt-get install -y /tmp/chrome.deb --no-install-recommends --allow-downgrades fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
&& rm /tmp/chrome.deb
ENV PUPPETEER_EXECUTABLE_PATH "/usr/bin/google-chrome"
#####################
#END PUPPETEER RECIPE
#####################
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["ProjectName.Core/ProjectName.Core.csproj", "ProjectName.Core/"]
RUN dotnet restore "ProjectName.Core/ProjectName.Core.csproj"
COPY . .
WORKDIR "/src/ProjectName.Core"
RUN dotnet build "ProjectName.Core.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "ProjectName.Core.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProjectName.Core.dll"]
Implementation
public static async Task<Stream> GeneratePdfFromHtml(string html, List<string> styleUrls = null)
{
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Args = new[] { "--no-sandbox" },
Headless = true
});
using (var page = await browser.NewPageAsync())
{
await page.SetContentAsync(html);
if(styleUrls != null)
foreach (var url in styleUrls)
{
await page.AddStyleTagAsync(new AddTagOptions { Path = url });
}
return await page.PdfStreamAsync();
}
}
It's probably worth noting that the docker implementation of puppeteer-sharp also seems to have a problem with finding stylesheets using a local url (e.g. localhost:1234/lib/bootsrap/bootsrap.min.css). It just hangs and spins forever, hence changing the AddStyleTagAsync()
to use filepaths instead. Don't forget, this is found under IHostingEnvironment.WebRootPath to save you a google search.
Hope this helps someone else out there. No doubt it'll cross my path again at some point in the not-so-distant future too!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With