I'm trying to have a docker container running on google cloud run, which is containing a simple nodejs app and google chromium headless to create a PDF from HTML source. Unfortunately, Google Cloud Run seems to have issues with thatever solution I try.
My Docker image(s) run perfectly locally and on other providers (i.E. Azure), but GCP just does not work.
What I try:
Basically building any docker image, installing node, npm, chromium, then running chromium --headless in the background. Then running the node app. The Node app is simply trying to connect to 127.0.0.1:9222 => that doesn't work on GCP, but anywhere else.
I tried with the official node images of docker hub I tried with an alpine image I tried with a debian image All of these run fine locally, but not on google cloud run.
Here's my latest test with a debian image:
FROM debian:latest
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
nodejs yarn npm chromium \
&& apt-get clean \
&& apt-get autoclean
RUN adduser --home /home/node --disabled-password --gecos "" node \
&& mkdir /home/node/app \
&& chown -R node:node /home/node/app
RUN apt-get install -y wget
RUN npm cache clean -f
RUN npm i -g n
RUN n stable
RUN node --version
RUN npm --version
USER node
WORKDIR /home/node/app
ENV CHROME_BIN=/usr/bin/chromium \
CHROME_PATH=/usr/lib/chromium/
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
COPY --chown=node . .
RUN npm install
RUN npm run build
ENV HOST=0.0.0.0 PORT=3000
EXPOSE ${PORT}
ENTRYPOINT [ "sh", "-c", "/home/node/app/docker-inside-start.sh" ]
Entrypoint sh is this:
#!/bin/sh
exec $CHROME_BIN --headless --use-gl=swiftshader \
--disable-software-rasterizer --disable-dev-shm-usage --remote-debugging-port=9222 \
--remote-debugging-address=0.0.0.0 --no-sandbox --disable-gpu \
--no-first-run --no-crash-upload --no-pings --no-wifi &
node .
My Node code looks like this. Nothing fancy.. APP_CONFIG_CHROME_HOST is set to localhost or 127.0.0.1 - nothing works
import * as htmlPdf from 'html-pdf-chrome';
// ...
private createPdf(html: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
const options: htmlPdf.CreateOptions = {
host: process.env.APP_CONFIG_CHROME_HOST,
port: 9222, // port Chrome is listening on
};
htmlPdf.create(html, options)
.then((pdf) => resolve(pdf.toBase64()))
.catch(e => reject(e));
} catch (err) {
reject(err);
}
});
}
Finally the errors/output I see :
2020-09-12 15:26:02.786 MESZ[0912/132602.785920:WARNING:discardable_shared_memory_manager.cc(194)] Less than 64MB of free space in temporary directory for shared memory files: 0
2020-09-12 15:26:38.827 MESZ[0912/132638.826713:ERROR:address_tracker_linux.cc(201)] Could not bind NETLINK socket: Permission denied (13)
2020-09-12 15:26:38.828 MESZ[0912/132638.828564:ERROR:file_path_watcher_linux.cc(71)] Failed to read /proc/sys/fs/inotify/max_user_watches
...
2020-09-12 15:28:28.364 MESZ[2020-09-12T13:28:26.764Z] [ERROR] <mail.service.js> Error: socket hang up
Any ideas anyone?
Note: the errors when using for example an alpine image are different. Googling points to hints that gcp has a general issue with some other part there. To be specific, in gvisor: https://github.com/google/gvisor/issues/1739#issuecomment-673861090 In case you want to experience that, heres a docker image doing the same like the above one but on alpine: chrome doesn't run there and exits with complaining about missing membarrier syscall
FROM alpine:latest
ARG BUILD_DATE
ARG VCS_REF
LABEL org.label-schema.build-date=$BUILD_DATE \
org.label-schema.description="Chrome running in headless mode in a tiny Alpine image" \
org.label-schema.name="alpine-chrome" \
org.label-schema.schema-version="1.0.0-rc1" \
org.label-schema.usage="https://github.com/Zenika/alpine-chrome/blob/master/README.md" \
org.label-schema.vcs-url="https://github.com/Zenika/alpine-chrome" \
org.label-schema.vcs-ref=$VCS_REF \
org.label-schema.vendor="Zenika" \
org.label-schema.version="latest"
# Installs latest Chromium package.
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/main" > /etc/apk/repositories \
&& echo "http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \
&& echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \
&& echo "http://dl-cdn.alpinelinux.org/alpine/v3.11/main" >> /etc/apk/repositories \
&& apk upgrade -U -a \
&& apk add --no-cache \
libstdc++ \
chromium \
harfbuzz \
nss \
freetype \
ttf-freefont \
wqy-zenhei \
tini make gcc g++ python git nodejs nodejs-npm yarn \
&& rm -rf /var/cache/* /var/lib/apt/lists/* /usr/share/man /tmp/*
RUN mkdir -p /home/node/app \
&& adduser -D node \
&& chown -R node:node /home/node/app
USER node
WORKDIR /home/node/app
ENV CHROME_BIN=/usr/bin/chromium-browser \
CHROME_PATH=/usr/lib/chromium/
# set ENV to development if NOT given in command line
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
COPY --chown=node package*.json ./
RUN npm install
COPY --chown=node . .
RUN npm run build
ENV HOST=0.0.0.0 PORT=3000
EXPOSE ${PORT}
ENTRYPOINT ["/sbin/tini", "--"]
CMD [ "sh", "-c", "/home/node/app/docker-inside-start.sh" ]