Виправлення помилки відсутності бібліотеки libssl.so.1.1 у Prisma в Alpine Docker
Мотор запитів Prisma зупиняється у образах Alpine Docker через відсутність файлу libssl.so.1.1. Що насправді відбувається з OpenSSL та як назавжди вирішити цю проблему в Node.js.
Джерела: 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 як стандартом
Через це конкретна помилка залежить від того, які версії 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 воно вимагає, встановіть його відповідним чином та фіксуйте версії, щоб поведінка залишалась прогнозованою незалежно від місця запуску контейнера.