Automattic / node-canvas

Node canvas is a Cairo backed Canvas implementation for NodeJS.
10.2k stars 1.17k forks source link

Error loading shared library libcairo.so.2 #1952

Open Jakin-Liu opened 2 years ago

Jakin-Liu commented 2 years ago

this is error detail info: Error: Error loading shared library libcairo.so.2: No such file or directory (needed by /app/node_modules/canvas/build/Release/canvas.node) at Object.Module._extensions..node (internal/modules/cjs/loader.js:1057:18) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Module.require (internal/modules/cjs/loader.js:887:19) at Module.Hook._require.Module.require (/usr/local/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:80:39) at require (internal/modules/cjs/helpers.js:74:18) at Object. (/app/node_modules/canvas/lib/bindings.js:3:18) at Module._compile (internal/modules/cjs/loader.js:999:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Module.require (internal/modules/cjs/loader.js:887:19) at Module.Hook._require.Module.require (/usr/local/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:80:39) at require (internal/modules/cjs/helpers.js:74:18) at Object. (/app/node_modules/canvas/lib/canvas.js:9:18) at Module._compile (internal/modules/cjs/loader.js:999:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Module.require (internal/modules/cjs/loader.js:887:19) at Module.Hook._require.Module.require (/usr/local/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:80:39) at require (internal/modules/cjs/helpers.js:74:18) at Object. (/app/node_modules/canvas/index.js:1:16) at Module._compile (internal/modules/cjs/loader.js:999:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Module.require (internal/modules/cjs/loader.js:887:19)

this is my dockfile: FROM node:12-alpine as base

WORKDIR /app COPY package.json . COPY package-lock.json . RUN apk add --update --no-cache \ make \ python3 \ g++ \ cairo-dev \ pango \ pango-dev \ jpeg-dev \ giflib-dev \ librsvg \ libpng \ libjpeg

when i run in docker, it show me that Error loading shared library libcairo.so.2: No such file or directory. It can run in my local computer

zbjornson commented 2 years ago

There's not enough info here to troubleshoot unless there are other errors in the docker build log that you can provide, sorry.

adamgins commented 2 years ago

Hi @Jakin-Liu , did you resolve this? - i am getting same issue, my docker file

# The tag here should match the Meteor version of your app, per .meteor/release
FROM geoffreybooth/meteor-base:2.6.1

# Copy app package.json and package-lock.json into container
COPY ./app/package*.json $APP_SOURCE_FOLDER/

RUN bash $SCRIPTS_FOLDER/build-app-npm-dependencies.sh

# Copy app source into container
COPY ./app $APP_SOURCE_FOLDER/

RUN bash $SCRIPTS_FOLDER/build-meteor-bundle.sh

# Use the specific version of Node expected by your Meteor release, per https://docs.meteor.com/changelog.html; this is expected for Meteor 2.6.1
FROM node:14.18.3-alpine

ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker

# Install OS build dependencies, which stay with this intermediate image but don’t become part of the final published image
RUN apk add --no-cache\
    bash \
    g++ \
    make \
    python3

RUN apk add --no-cache \
    build-base  cairo-dev cairo cairo-tools \
    # pillow dependencies
    pango pango-dev jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev    

RUN pip install "flask==1.0.1" "CairoSVG==2.1.3"

RUN npm install canvas@2.9.0

# Copy in entrypoint
COPY --from=0 $SCRIPTS_FOLDER $SCRIPTS_FOLDER/

# Copy in app bundle
COPY --from=0 $APP_BUNDLE_FOLDER/bundle $APP_BUNDLE_FOLDER/bundle/

RUN bash $SCRIPTS_FOLDER/build-meteor-npm-dependencies.sh --build-from-source

# Start another Docker stage, so that the final image doesn’t contain the layer with the build dependencies
# See previous FROM line; this must match
FROM node:14.18.3-alpine

ENV APP_BUNDLE_FOLDER /opt/bundle
ENV SCRIPTS_FOLDER /docker

# Install OS runtime dependencies
RUN apk --no-cache add \
    bash \
    ca-certificates

# Copy in entrypoint with the built and installed dependencies from the previous image
COPY --from=1 $SCRIPTS_FOLDER $SCRIPTS_FOLDER/

# Copy in app bundle with the built and installed dependencies from the previous image
COPY --from=1 $APP_BUNDLE_FOLDER/bundle $APP_BUNDLE_FOLDER/bundle/

# Start app
ENTRYPOINT ["/docker/entrypoint.sh"]

CMD ["node", "main.js"]

this is the error I get

pp_1    | /opt/bundle/bundle/programs/server/node_modules/fibers/future.js:280
app_1    |                      throw(ex);
app_1    |                      ^
app_1    | 
app_1    | Error: Error loading shared library libcairo.so.2: No such file or directory (needed by /opt/bundle/bundle/programs/server/npm/node_modules/canvas/build/Release/canvas.node)
app_1    |     at Object.Module._extensions..node (internal/modules/cjs/loader.js:1144:18)
app_1    |     at Module.load (internal/modules/cjs/loader.js:950:32)
app_1    |     at Module.Mp.load (/opt/bundle/bundle/programs/server/runtime.js:46:33)
app_1    |     at Function.Module._load (internal/modules/cjs/loader.js:790:12)
app_1    |     at Module.require (internal/modules/cjs/loader.js:974:19)
app_1    |     at require (internal/modules/cjs/helpers.js:93:18)
app_1    |     at Object.<anonymous> (/opt/bundle/bundle/programs/server/npm/node_modules/canvas/lib/bindings.js:3:18)
app_1    |     at Module._compile (internal/modules/cjs/loader.js:1085:14)
app_1    |     at Module.Mp._compile (/opt/bundle/bundle/programs/server/runtime.js:99:23)
app_1    |     at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
app_1    |     at Module.load (internal/modules/cjs/loader.js:950:32)
app_1    |     at Module.Mp.load (/opt/bundle/bundle/programs/server/runtime.js:46:33)
app_1    |     at Function.Module._load (internal/modules/cjs/loader.js:790:12)
app_1    |     at Module.require (internal/modules/cjs/loader.js:974:19)
app_1    |     at require (internal/modules/cjs/helpers.js:93:18)
app_1    |     at Object.<anonymous> (/opt/bundle/bundle/programs/server/npm/node_modules/canvas/lib/canvas.js:9:18) {
app_1    |   code: 'ERR_DLOPEN_FAILED'
app_1    | }
siawyoung commented 2 years ago

Getting this error as well.

fabriciosautner commented 1 year ago

Same problem

alvindera97 commented 1 year ago

It's been a year and a month now... Wow.... Just wow.

michalflog commented 1 year ago

Spent some time resolving this problem on alpine:

  1. Packages required by canvas to compile: python3 g++ make cairo-dev pango-dev
  2. Packages required by canvas while running: cairo pango

ad.1 While running npm install or yarn install canvas package uses node-gyp to compile canvas package on the machine. It is necessary to include this packages or else the similar error will be thrown:

Failed to execute '/usr/local/bin/node /usr/local/lib/node_modules/npm/node_mgyp/bin/node-gyp.js build --fallback-to-build --update-binary --module=/app/node_moduleld/Release/canvas.node --module_name=canvas --module_path=/app/node_modules/canvas/builnapi_version=8 --node_abi_napi=napi --napi_build_version=0 --node_napi_label=node-v93' 

Rest of additional packages like jpeg-dev are not mandatory, but will be needed if you want to use more of the functionality of canvas package.

ad.2 While running your app /app/node_moduleld/Release/canvas.node uses shared libraries that need to be on your machine. Those libraries are provided by packages cairo pango. Without for example cairo you will get error:

Error loading shared library libcairo.so.2: No such file or directory

If you've added jpeg-dev package i think you should add corresponding packages with shared libraries

@Jakin-Liu You need to add cairo package to you apk add to get rid of that error

@adamgins You're adding packages cairo pango in the first stage. They won't end up in the second stage where you fire the app, which means that /app/node_moduleld/Release/canvas.node won't be able to find required shared libraries. Add cairo pango to your apk add in second stage. That should make it.

Hope it helps you all 😄

tolgaand commented 1 year ago

Same issue.

My DockerFile

FROM node:alpine AS BUILDER

WORKDIR /src
ADD package.json package-lock.json ./

RUN apk add --update --no-cache make python3 g++ cairo cairo-dev pango pango-dev jpeg-dev giflib-dev libpng libjpeg
RUN npm install --build-from-source
RUN npm rebuild canvas --build-from-source

FROM node:alpine

WORKDIR /src

COPY --from=BUILDER /src/node_modules ./node_modules

ENV NODE_ENV=production

COPY package.json ./
COPY src ./src
COPY tsconfig.json ./tsconfig.json

CMD ["npm", "run", "prod"]
michalflog commented 1 year ago

@tolgaand What is the error that you get?

tolgaand commented 1 year ago

@tolgaand What is the error that you get?

Screenshot 2023-02-24 at 14 35 56
michalflog commented 1 year ago

@tolgaand In your Dockerfile, you have 2 stages:

  1. BUILDER
  2. stage where you run your app.

As stage BUILDER looks fine (you can remove cairo and pango from apk add and I think you don't need to use --build-from-source on install and canvas) there is a problem in second stage.

Add RUN apk add --no-cache cairo pango in second stage. While running the app, canvas requires those packages at runtime. Installed at first stage, they are not making it to the second final stage.

HoaX7 commented 1 year ago

hi @michalflog I'm using the RUN apk add cairo pango in the second stage, but im getting the error that this package does not exist. Here's my docker file

# syntax = docker/dockerfile:1

# Adjust NODE_VERSION as desired
ARG NODE_VERSION=17.6.0
FROM node:${NODE_VERSION}-alpine as base

LABEL fly_launch_runtime="Node.js"

# Node.js app lives here
WORKDIR /app

# Set production environment
ENV NODE_ENV=production

# Throw-away build stage to reduce size of final image
FROM base as build

# To install node canvas
RUN apk add --no-cache \
        git \
        build-base \
        g++ \
        cairo-dev \
        jpeg-dev \
        pango-dev \
        freetype-dev \
        giflib-dev \
        make \
        python3

# Install node modules
COPY --link package-lock.json package.json ./
RUN npm ci --include=dev

# Copy application code
COPY --link . .

# Build application
RUN npm run build

# Remove development dependencies
RUN npm prune --omit=dev

# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app /app

RUN apk add  --no-cache cario pango

# Setup sqlite3 on a separate volume
RUN mkdir -p /data
VOLUME /data
ENV DATABASE_URL="file:///data/sqlite.db"

ENV NODE_PATH=lib/
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD [ "npm", "run", "start" ]
michalflog commented 1 year ago

@HoaX7 what is an exact error that you're getting?

HoaX7 commented 1 year ago

I got the error "Cairo" package was not available, but I was able to resolve it

michalflog commented 1 year ago

@HoaX7 was it a typo in cairo name from my answer above?

pavel1860 commented 1 year ago

I am getting also that error since today:

0 52.89 Error: Error loading shared library libcairo.so.2: No such file or directory (needed by /app/node_modules/canvas/build/Release/canvas.node)

0 52.89 at Object.Module._extensions..node (node:internal/modules/cjs/loader:1282:18)

0 52.89 at Module.load (node:internal/modules/cjs/loader:1076:32)

0 52.89 at Function.Module._load (node:internal/modules/cjs/loader:911:12)

0 52.89 at Module.require (node:internal/modules/cjs/loader:1100:19)

0 52.89 at require (node:internal/modules/cjs/helpers:108:18)

0 52.89 at Object. (/app/node_modules/canvas/lib/bindings.js:3:18)

0 52.89 at Module._compile (node:internal/modules/cjs/loader:1198:14)

0 52.89 at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10)

0 52.89 at Module.load (node:internal/modules/cjs/loader:1076:32)

0 52.89 at Function.Module._load (node:internal/modules/cjs/loader:911:12) {

0 52.89 code: 'ERR_DLOPEN_FAILED'

0 52.89 }

0 52.89

0 52.89 > Build error occurred

0 52.89 Error: Failed to collect page data for /movie/[movieid]/editor

0 52.89 at /app/node_modules/next/dist/build/utils.js:916:15 {

0 52.89 type: 'Error'

0 52.89 }

and this is my Dockerfile:

FROM node:16-alpine AS deps 

RUN apk update
RUN apk add --update --no-cache openssl1.1-compat

RUN apk add --no-cache libc6-compat python3 g++ make && ln -sf python3 /usr/bin/python

RUN apk add --no-cache \
        sudo \
        curl \
        build-base \
        libpng \
        libpng-dev \
        jpeg-dev \
        pango-dev \
        # cairo-dev \
        cairo \
        giflib-dev \
        ;

RUN apk --no-cache add ca-certificates wget  && \
    wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.35-r1/glibc-2.35-r1.apk && \
    apk add glibc-2.35-r1.apk

WORKDIR /app

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ 
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ 
  elif [ -f package-lock.json ]; then npm ci --legacy-peer-deps; \ 
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ 
  else echo "Lockfile not found."  && exit 1; \
  fi

FROM node:16-alpine AS builder 
RUN apk add --update --no-cache openssl1.1-compat
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules 
COPY . .

RUN npx prisma generate

ARG hostfile
COPY $hostfile .env

RUN yarn build

FROM node:16-alpine AS runner 

RUN apk add --update --no-cache openssl1.1-compat
VOLUME [ “/sys/fs/cgroup” ]
RUN apk add --no-cache bash

WORKDIR /app

ENV NODE_ENV=production

RUN addgroup -g 1001 -S nodejs 
RUN adduser -S nextjs -u 1001 

COPY --from=builder /app/public ./public 

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]
michalflog commented 1 year ago

@pavel1860 runner is your final stage in Dockerfile where you run your app. You need to add RUN apk add --no-cache cairo pango in it. node-canvas needs this packages at runtime.

PS. you can remove cairo package from

RUN apk add --no-cache \
        sudo \
        curl \
        build-base \
        libpng \
        libpng-dev \
        jpeg-dev \
        pango-dev \
        # cairo-dev \
        cairo \
        giflib-dev \
        ;

in deps stage as it is not needed here. And I think you need to uncomment the cairo-dev package as it is needed, but I may be wrong.

pavel1860 commented 1 year ago

it doesn't work. I still has the same error. I was using this dockerfile for a few months and today something happend. here is the updated docker file:

FROM node:16-alpine AS deps 

RUN apk update
RUN apk add --update --no-cache openssl1.1-compat
RUN apk add --no-cache libc6-compat python3 g++ make && ln -sf python3 /usr/bin/python

RUN apk add --no-cache \
        sudo \
        curl \
        build-base \
        libpng \
        libpng-dev \
        jpeg-dev \
        pango-dev \
        cairo-dev \
        giflib-dev \
        ;

RUN apk --no-cache add ca-certificates wget  && \
    wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.35-r1/glibc-2.35-r1.apk && \
    apk add glibc-2.35-r1.apk

WORKDIR /app

COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ 
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ 
  elif [ -f package-lock.json ]; then npm ci --legacy-peer-deps; \ 
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ 
  else echo "Lockfile not found."  && exit 1; \
  fi

FROM node:16-alpine AS builder 
RUN apk add --update --no-cache openssl1.1-compat
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules 
COPY . .

RUN npx prisma generate

ARG hostfile
COPY $hostfile .env

RUN yarn build

FROM node:16-alpine AS runner 

RUN apk add --update --no-cache openssl1.1-compat
VOLUME [ “/sys/fs/cgroup” ]
RUN apk add --no-cache bash

RUN apk add  --no-cache cairo pango

WORKDIR /app

ENV NODE_ENV=production

RUN addgroup -g 1001 -S nodejs 
RUN adduser -S nextjs -u 1001 

COPY --from=builder /app/public ./public 

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]
SawkaDev commented 1 year ago

Same issue, following.

FROM node:18-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
# RUN apk add --no-cache libc6-compat
WORKDIR /app

# https://github.com/Automattic/node-canvas/issues/866
RUN apk add --update --no-cache \
    make \
    g++ \
    jpeg-dev \
    cairo-dev \
    giflib-dev \
    pango-dev

RUN echo "Installed packages:" && apk info -vv \
    && echo "\nEnvironment variables:" && printenv

RUN ls -l /usr/lib/libcairo.so.2*

COPY --link package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --link --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1

COPY --link --from=builder /app/public ./public

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --link --from=builder /app/.next/standalone ./
COPY --link --from=builder /app/.next/static ./.next/static
COPY --link --from=builder /app/global-bundle.pem ./

CMD ["node", "server.js"]

If I run a LDD on the canvas.node I get

/app/node_modules/canvas/build/Release # ldd canvas.node 
        /lib/ld-musl-x86_64.so.1 (0x7f4ae5aee000)
Error loading shared library libcairo.so.2: No such file or directory (needed by canvas.node)
Error loading shared library libpng16.so.16: No such file or directory (needed by canvas.node)
Error loading shared library libpangocairo-1.0.so.0: No such file or directory (needed by canvas.node)
Error loading shared library libpango-1.0.so.0: No such file or directory (needed by canvas.node)
Error loading shared library libgobject-2.0.so.0: No such file or directory (needed by canvas.node)
Error loading shared library libglib-2.0.so.0: No such file or directory (needed by canvas.node)
Error loading shared library libfreetype.so.6: No such file or directory (needed by canvas.node)
Error loading shared library libjpeg.so.8: No such file or directory (needed by canvas.node)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x7f4ae57f2000)
        libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7f4ae57ce000)
        libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f4ae5aee000)
Error relocating canvas.node: _ZNK2v85Value8ToUint32ENS_5LocalINS_7ContextEEE: symbol not found
Error relocating canvas.node: pango_layout_get_line: symbol not found

@michalflog do you have any advice, it seems you have gotten this to work

EDIT: I was able to get it to work by moving the apk add cairo pango to the runner stage, not build stage!!

rettgerst commented 8 months ago

I'd like to add some extra detail for anyone else facing this issue in the future - this isn't node-canvas specific but may help people with related or similar issues:

  1. while node-canvas only needs cairo and pango, your project may need more packages - mine also needed gcompat and libuuid - if you encounter an "Error loading shared library" error you can type the name of the shared library file (e.g. libuuid.so.1) into the Alpine Package Search page to find the name of the package that provides the shared library.
  2. for next.js projects, you need to add the packages to both the builder and the runner stages, as the next build command executes all of your code as part of the build process ("collecting page data") to detect getStaticProps.

hope this saves someone some trouble!

gdev219 commented 6 months ago

For anyone facing issues especially with Three.js in a Next.js/React app, use node-slim instead of node-alpine. This will solve the problem.