Головна / Статті

Виправлення помилки відсутності бібліотеки libssl.so.1.1 у Prisma в Alpine Docker

Мотор запитів Prisma зупиняється у образах Alpine Docker через відсутність файлу libssl.so.1.1. Що насправді відбувається з OpenSSL та як назавжди вирішити цю проблему в Node.js.

Видавець: REACTAPP.TOP6 хв читання1479 слів

Джерела: Prisma Docker Error: `libssl.so.1.1` Not Found on Alpine

Ви написали Dockerfile, розпочали процес будування, і все виглядало нормально. Потім ви запустили docker compose up, стежили за потоком логів, і саме у момент, коли додаток мав під’єднатися до бази даних, він зупинився.

Симптоми

Якщо щось із цього здається вам знайомим, пояснення та спосіб виправлення наведені нижче:

  • Звіт про збій, у якому йдеться про те, що спільну бібліотеку під назвою libssl.so.1.1 не вдалося знайти ніде у файловій системі
  • Помилка під час запуску Prisma з повідомленням PrismaClientInitializationError: Unable to require libquery_engine-linux-musl.so.node
  • Повідомлення про те, що Prisma не змогла визначити версію libssl/openssl, яка використовується
  • Контейнер, запущений за допомогою docker compose up, майже відразу ж зупиняється
  • Компіляція завершується без жодних проблем, проте контейнер зупиняється відразу після спроби зв’язку з базою даних
  • Повна помилка зазвичай виглядає приблизно так:

    Details: Error loading shared library libssl.so.1.1: No such file or directory
    (needed by /app/node_modules/.prisma/client/libquery_engine-linux-musl.so.node)
    

    Стек

    Ситуація, в якій це виникло, стосувалась API на TypeScript у Node.js/Express, Prisma як шара ORM та PostgreSQL як бази даних. Dockerfile був створений на основі node:20-alpine — це розумний стандарт, адже образи на основі Alpine значно легші за свої аналоги на Debian: їх швидше завантажують, менше можливостей для вразливостей та вони краще підходять для роботи у продакшені.

    Сам процес збірки пройшов без проблем. Це була багатоетапна налаштування: TypeScript було скомпільовано у папку dist/, а клієнт Prisma було правильно створено. Під час збірки не було жодних помилок. Проблеми з’явилися лише тоді, коли команда docker compose up фактично запустила контейнер.

    Що насправді відбувається

    Alpine Linux навмисно спрощений. У цьому й полягає вся привабливість — саме тому отримані образи такі маленькі. Але через свою мінімальність Alpine не включає багато пакетів, які за замовчуванням є у образах на базі Debian чи Ubuntu.

    Механізм запитів Prisma постачається у вигляді заздалегідь скомпільованого бінарного файлу — це файл .so (спільний об’єкт), який входить до складу вашого проекту та має назву, схожу на libquery_engine-linux-musl.so.node. Саме цей бінарний файл відповідає за безпосередню комунікацію з вашою базою даних, і для його роботи необхідний OpenSSL.

    Важливо те, що цей бінарний файл прив’язаний до певної версії OpenSSL, а не до OpenSSL як такого концепту. Версія OpenSSL, яку він очікує, залежить як від версії Prisma, так і від версії Alpine:

    • Ранні версії двигуна Prisma прив’язані до OpenSSL 1.1, який надає файл libssl.so.1.1
    • Новіші версії Prisma створені для роботи з OpenSSL 3, який надає файл libssl.so.3
    • Версії Alpine до 3.16 поставлялися з OpenSSL 1.1 як стандартом
  • Alpine 3.17 та новіші версії постачають OpenSSL 3 замість нього
  • Через це конкретна помилка залежить від того, які версії Prisma та Alpine ви поєднуєте. Уважно прочитайте текст помилки — у ньому вказана саме та бібліотека, якої бракує, а libssl.so.1.1 та libssl.so.3 вимагають різних пакетів та різних способів виправлення.

    Причина, чому це проявляється лише під час виконання, а не під час компіляції, полягає у тому, що prisma generate, який виконується на етапі компіляції, насправді ніколи не запускає бінарний файл двигуна запитів. Він лише створює клієнтський код на TypeScript. Сам бінарний файл двигуна запускається лише тоді, коли програма починає працювати та Prisma намагається відкрити з’єднання до бази даних. На цьому етапі ви вже знаходитеся всередині запущеного контейнера з несумісною або відсутньою бібліотекою OpenSSL, і до збою вже немає жодних попереджень.

    Рішення

    Рішення полягає у прямій установці OpenSSL у ваш Dockerfile за допомогою менеджера пакетів Alpine, apk. Який саме пакет вам потрібен, залежить повністю від інформації, наданої у повідомленні про помилку.

    Якщо у повідомленні про помилку згадується libssl.so.1.1 (OpenSSL 1.1):

    У новіших версіях Alpine (3.17 та вище) OpenSSL 1.1 більше не встановлюється за замовчуванням, тому вам потрібен пакет сумісності:

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

    Якщо у повідомленні про помилку згадується libssl.so.3 (OpenSSL 3), або ви використовуєте останню версію Prisma на новій версії Alpine:

    RUN apk add --no-cache openssl
    

    Це завантажує ту версію OpenSSL, яка надається репозиторієм пакетів Alpine для цієї версії — у Alpine 3.17+ це OpenSSL 3.

    Найбезпечнішою довгостроковою стратегією є навмисне фіксування версій. Виправте версію Alpine за допомогою тега базового зображення, а версію Prisma закріпіть у файлі package.json, щоб вимоги до OpenSSL залишалися консистентними та не змінювалися несподівано в різних середовищах. У власних примітках до випуску Prisma перелічено, проти якої версії OpenSSL скомпільовано кожен бінарний файл движка.

    Якщо ваша конфігурація використовує багатоступеневе компілювання, вам потрібно додати цей рядок у обидві стадії, оскільки кожна з них працює на Alpine окремо:

    # Stage 1: Builder
    FROM node:20-alpine AS builder
    RUN apk add --no-cache openssl
    
    WORKDIR /app
    COPY package*.json ./
    COPY prisma ./prisma
    RUN npm install
    COPY . .
    RUN npx prisma generate
    RUN npm run build
    
    # Stage 2: Production
    FROM node:20-alpine AS production
    RUN apk add --no-cache openssl
    
    WORKDIR /app
    ENV NODE_ENV=production
    
    COPY package*.json ./
    COPY prisma ./prisma
    RUN npm install --omit=dev
    COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
    COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
    COPY --from=builder /app/dist ./dist
    
    EXPOSE 4000
    CMD ["node", "dist/server.js"]
    

    Прапорець --no-cache запобігає тому, щоб apk зберігав локальну копію індексу пакетів, що дозволяє зберегти невеликий розмір кінцевого зображення — що відповідає самій причині, чому ви спочатку обрали Alpine.

    Чому просто не перейти на node:slim?

    Це цілком обґрунтоване запитання. node:20-slim побудований на Debian, а не на Alpine, і Debian постачає OpenSSL вже готовим до використання, тому цей конкретний спосіб збою майже не трапляється. Багато команд просто роблять цю заміну та продовжують працювати далі.

    Проблема полягає у розмірі зображень. Типовий образ Node на базі Alpine має розмір близько 50–70 МБ, тоді як еквівалентний образ на Debian slim сягає 150–200 МБ. Якщо врахувати десятки розгортань, кілька сервісів та постійні завантаження кожного члена команди, ця різниця швидко накопичується. Крім того, Alpine за замовчуванням має меншу поверхню атак, що є важливим фактором у ситуаціях, де важлива безпека.

    Встановлення відсутнього пакету за допомогою apk add — це поширений підхід, який прямо описаний у посібниках Prisma, і ви вже знайдете його у безлічі продакшн-Dockerfile. Перехід на образ, заснований на Debian, щоб уникнути проблем із збігом версій, є цілком обґрунтованим вибором, але все одно варто зрозуміти, чому взагалі виникає така невідповідність, незалежно від того, який базовий образ ви оберете.

    Примітка щодо багатостадійних збірок

    Якщо два рядки FROM та синтаксис --from=builder у попередньому Dockerfile здалися вам незнайомими, ось коротке пояснення причин такої структури.

    Dockerfile з однією стадією, яка компілює TypeScript, призводить до того, що компілятор TypeScript ts-node, усі devDependency та ваші необроблені вихідні файли потрапляють безпосередньо до продакшн-образу. Жоден з цих елементів не повинен там бути — вони розширюють образ та збільшують кількість елементів, схильних до потенційних вразливостей.

    Багатостадійна компіляція забезпечує належне розділення елементів: стадія компіляції містить усе необхідне для обробки проекту, тоді як стадія продакшну починається з чистої основи та включає лише те, що дійсно потрібне під час виконання — скомпільований вихідний каталог dist/, залежності, необхідні лише для продакшну, та створений Prisma Client. Усе зайве зі стадії компіляції автоматично залишається позаду під час створення остаточного образу Docker.

    У результаті ви отримуєте легкий образ, готовий до використання в продакшні, який не несе з собою власних інструментів компіляції.

    Після виправлення

    Якщо встановлена правильна версія OpenSSL, запуск docker compose up нарешті має дати вам бажаний результат:

    ✅ Connected to PostgreSQL
    🚀 Server listening on port 4000
    

    Якщо ваша конфігурація Prisma працює через Docker Compose, наступним кроком є застосування міграцій до бази даних у контейнері, а не до тієї, що працює локально. Іншими словами, потрібно виконати команду docker compose exec app npx prisma migrate deploy, яка виконує команду міграції всередині контейнера запущеного додатку — де DATABASE_URL вказує на сервіс Postgres, яким керує Compose, а не на будь-який інший ресурс на вашому хост-комп’ютері.

    Помилка OpenSSL налякує при першій зустрічі — це довгий блок бінарного виводу, що закінчується значенням ENOENT. Однак коренева причина є вузькою та конкретною: двигун запитів Prisma — це скомпільований бінарний файл, створений під певну версію OpenSSL, а Alpine не гарантує наявності саме цієї версії. Уважно прочитайте повідомлення про помилку, з’ясуйте, який саме libssl воно вимагає, встановіть його відповідним чином та фіксуйте версії, щоб поведінка залишалась прогнозованою незалежно від місця запуску контейнера.