coletiv / puppeteer-pdf

PDF generation wrapper for Elixir using Puppeteer

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Running on alpine

PJUllrich opened this issue · comments

I encountered a range of dependency problems, when I tried to run this in an alpine docker container. I wanted to share how to make this run with (elixir) puppeteer-pdf=1.0.3 and (node) puppeteer-pdf=1.2.0. The main source of making this work was the documentation on the official Puppeteer GitHub page.

Prepare your alpine Docker container

I used this Dockerfile for building the container:

RUN apk add --update 
RUN apk update && apk upgrade && \
    echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
    echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
    apk add --no-cache \
    nodejs \
    nodejs-npm \
    inotify-tools \
    chromium@edge=~73.0.3683.103 \
    nss@edge \
    freetype@edge \
    freetype-dev@edge \
    harfbuzz@edge \
    ttf-freefont@edge

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
RUN npm install puppeteer-pdf@1.2.0 puppeteer@1.12.2 -g

COPY assets/config/puppeteer-pdf.js /usr/lib/node_modules/puppeteer-pdf

Set launch arguments in puppeteer-pdf.js

Change the following line in your /usr/lib/node_modules/puppeteer-pdf/puppeteer-pdf.js file:

# Before
const browser = await puppeteer.launch({args: ["--no-sandbox"] });

# After
const browser = await puppeteer.launch({executablePath: '/usr/bin/chromium-browser', args: ["--no-sandbox", "--disable-software-rasterizer", "--headless", "--disable-gpu", "--disable-dev-shm-usage"] });

Now, you "should" be able to use the (elixir) puppeteer-pdf library on alpine as well!

Problems encountered

Just for documentation purposes, I'm adding some error logs that I've encountered until I found this solution.

Without settting the --disable-gpu and --disable-software-rasterizer flag

Failed to load /usr/lib/chromium/swiftshader/libGLESv2.so: Error loading shared library /usr/lib/chromium/swiftshader/libGLESv2.so: No such file or directory

Bonus: Insert your own .css file.

In your puppeteer-pdf.js file, add the following line:

...  
# Add the following line:
await page.addStyleTag({path: '/src/priv/static/css/app.css'}); // <--- Add this line
await page.pdf(options);

await browser.close();
...

Hi @PJUllrich, thanks for sharing your configuration for the Docker Alpine version. I will try it to check if everything is working, but can you explain if the versions set on your example need to be fixed, or can be the last version ?

chromium@edge=~73.0.3683.103 \
...
RUN npm install puppeteer@1.12.2 puppeteer-pdf@1.2.0 -g

Do we really need all this flags ?

["--no-sandbox", "--disable-software-rasterizer", "--headless", "--disable-gpu", "--disable-dev-shm-usage"] });

The puppeteer@1.12.2 version needs to be fixed according to the troubleshooting section. I also fixed the puppeteer-pdf version here so that this configuration won't break if for whatever reason puppeteer-pdf doesn't support puppeteer@1.12.2 anymore.

Unfortunately, these flags are necessary in order to make this run on alpine. I tested removing any of them and puppeteer-pdf won't start if any is removed. In the following a short explanation why these flags are needed:

  • --no-sandbox is needed if puppeteer-pdf is run as root. This flag can be omitted, if a dedicated user is added as explained in the toubleshooting section.
  • --headless is needed if puppeteer-pdf is run on a server without display/screen. Can be omitted, if puppeteer-pdf is run on a local development machine with desktop (i.e. your laptop/computer)
  • --disable-dev-shm-usage is recommended by the troubleshooting section since:

Docker runs a container with a /dev/shm shared memory space 64MB. This is typically too small for Chrome and will cause Chrome to crash when rendering large pages

  • --disable-gpu and --disable-software-rasterizer are related to the issue that the OpenGL ES and EGL libraries libGLESv2 and libEGL which are needed by chromium for rendering are only available on Debian, but not for alpine. The alpine versions of these libraries mesa-egl and mesa-gles apparently don't fix the issue. Therefore, the only option as of now is to disable the gpu rendering with these flags.

I hope this answers your questions :)

Yes, thanks for the detailed answer. I will check if everything is working, if you want you can open a PR to include this information you have written in the README.md, or I will add it later.

Thanks!

Please verify whether this works for you. I added a link to this issue to the Readme in #25

Thanks, I finally got time to test it. I notice that in your example you use COPY assets/config/puppeteer-pdf.js /usr/lib/node_modules/puppeteer-pdf to do the change on puppeteer-pdf.js. I think it is better to used sed to automate that action:

RUN sed -i 's~const browser = await puppeteer.launch({ args: \["--no-sandbox"\] });~const browser = await puppeteer.launch({executablePath: "/usr/bin/chromium-browser", args: \["--no-sandbox", "--disable-software-rasterizer", "--headless", "--disable-gpu", "--disable-dev-shm-usage"\] });~g'  /usr/lib/node_modules/puppeteer-pdf/puppeteer-pdf.js

Puppeteer-pdf executable will be on: /usr/bin/puppeteer-pdf

Here my Dockerbuild sample.

#
# Stage 1
#

FROM bitwalker/alpine-elixir-phoenix:1.8.1 as builder
ENV MIX_ENV=prod
WORKDIR /myapp

# Umbrella
COPY mix.exs mix.lock ./
COPY config config

RUN mix local.hex --force && \
    mix local.rebar --force

# Apps
COPY lib lib
COPY priv priv
RUN mix do deps.get, deps.compile

WORKDIR /myapp
COPY rel rel

RUN mix distillery.release --env=prod --verbose

#
# Stage 2
#

FROM bitwalker/alpine-elixir-phoenix:1.8.1

RUN apk add --update
RUN apk update && apk upgrade && \
    echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
    echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
    apk add --no-cache \
    nodejs \
    nodejs-npm \
    inotify-tools \
    chromium@edge=~73.0.3683.103 \
    nss@edge \
    freetype@edge \
    freetype-dev@edge \
    harfbuzz@edge \
    ttf-freefont@edge

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
RUN npm install puppeteer-pdf@1.2.0 puppeteer@1.12.2 -g

RUN sed -i 's~const browser = await puppeteer.launch({ args: \["--no-sandbox"\] });~const browser = await puppeteer.launch({executablePath: "/usr/bin/chromium-browser", args: \["--no-sandbox", "--disable-software-rasterizer", "--headless", "--disable-gpu", "--disable-dev-shm-usage"\] });~g'  /usr/lib/node_modules/puppeteer-pdf/puppeteer-pdf.js

WORKDIR /myapp
COPY --from=builder /myapp/_build/prod/rel/myapp/releases/*/myapp.tar.gz .

RUN tar zxf myapp.tar.gz && rm myapp.tar.gz
CMD ["/myapp/bin/myapp", "foreground"]

Please be aware that you need to turn around the npm install packages. Otherwise, the puppeteer-pdf package will install a newer version of puppeteer and this will break the puppeteer -> chrome version dependency. So, I'd recommend to change your Dockerbuild like this:

# Before
RUN npm install puppeteer@1.12.2 puppeteer-pdf@1.2.0 -g

# After
RUN npm install puppeteer-pdf@1.2.0 puppeteer@1.12.2 -g

I already updated my Dockerfile in my first post.

Is that a problem that happen right now ? When I tested I didn't have any issue generating the PDF.

Yes, it occurred only now. I cannot pinpoint why this happened now other than observing that puppeteer-pdf installed a higher version of puppeteer. PDF generation simply timed out in this case. It's weird, but this "should" solve it.