Strona główna / Artykuły

Rozwiązanie błędu braku biblioteki libssl.so.1.1 w Prisma na Alpine Dockerze

Silnik zapytań Prisma zawala się w obrazach Alpine Docker z brakującym plikiem libssl.so.1.1. Co tak naprawdę dzieje się z OpenSSL i jak naprawić to definitywnie w Node.js.

Wydawca: REACTAPP.TOP6 min czytania1479 słów

Źródła: Prisma Docker Error: `libssl.so.1.1` Not Found on Alpine

Napisałeś plik Dockerfile, rozpocząłeś proces budowania i wszystko wyglądało dobrze. Następnie uruchomiłeś docker compose up, obserwowałeś przepływ logów, a dokładnie w momencie, gdy aplikacja powinna była połączyć się z bazą danych, uległa awarii.

Symptomy

Jeśli cokolwiek z tego wydaje ci się znajome, wyjaśnienie i rozwiązanie znajdują się poniżej:

  • Raport o awarii wskazujący, że biblioteka współdzielona o nazwie libssl.so.1.1 nie mogła zostać znaleziona nigdzie w systemie plików
  • Błąd uruchamiania Prismy pokazujący PrismaClientInitializationError: Unable to require libquery_engine-linux-musl.so.node
  • Wiadomość informująca, że Prisma nie mogła określić, która wersja libssl/openssl jest używana
  • Kontener uruchomiony za pomocą docker compose up wyłącza się niemal natychmiast
  • Budowa, która kończy się bez żadnych problemów, ale kontener przestaje działać w momencie, gdy próbuje nawiązać połączenie z bazą danych
  • Pełny komunikat o błędzie zwykle wygląda mniej więcej tak:

    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)
    

    Stack

    Sytuacja, w której pojawił się ten problem, dotyczyła API opartego na TypeScript w Node.js/Express, warstwy ORM Prisma oraz bazy danych PostgreSQL. Dockerfile był oparty na node:20-alpine, co jest rozsądnym standardem, ponieważ obrazy oparte na Alpine są znacznie lżejsze od odpowiedników z Debianem — szybciej się ładują, mają mniejszą powierzchnię podatną na zagrożenia i są ogólnie lepiej przystosowane do działania w środowisku produkcyjnym.

    Sam proces budowania przebiegł bez problemów. Była to konfiguracja wieloetapowa: TypeScript został skompilowany do katalogu dist/, a klient Prisma został poprawnie wygenerowany. Podczas budowania nie wystąpiły żadne błędy. Problemy pojawiły się dopiero w momencie, gdy docker compose up faktycznie uruchomił kontener.

    Co tak naprawdę się dzieje

    Alpine Linux jest celowo uproszczony. To właśnie stanowi jego główną zaletę — dlatego powstające obrazy są tak małe. Jednak minimalizm oznacza, że Alpine pomija wiele pakietów, które obrazy oparte na Debianie lub Ubuntu zawierają domyślnie.

    Motor zapytań Prisma dostarczany jest jako uprzednio skompilowany plik binarny — plik .so (obiekt współdzielony) dołączony do Twojego projektu, w tym kontekście o nazwie mniej więcej libquery_engine-linux-musl.so.node. Ten plik binarny odpowiada za faktyczną komunikację z bazą danych i do działania wymaga bezpośrednio OpenSSL.

    Ważne jest to, że ten plik binarny motoru jest powiązany z jedną konkretną wersją OpenSSL, a nie z OpenSSL jako pojęciem ogólnym. Wersja, której oczekuje, zależy zarówno od wersji Prisma, jak i od wersji Alpine:

    • Wcześniejsze wersje motora Prisma były powiązane z OpenSSL 1.1, który udostępnia plik libssl.so.1.1
    • Nowsze wersje Prisma są budowane z użyciem OpenSSL 3, który udostępnia plik libssl.so.3
    • Wersje Alpine do 3.16 dostarczały jako standard OpenSSL 1.1
  • Alpine 3.17 i nowsze wersje dostarczają zamiast tego OpenSSL 3
  • Z powodu tego dokładny błąd, z którym się spotykasz, zależy od tego, jakie wersje Prisma i Alpine łączysz. Przeczytaj uważnie tekst błędu — podaje on nazwę konkretnej brakującej biblioteki wspólnej, a libssl.so.1.1 w porównaniu z libssl.so.3 wymaga innego pakietu i innej korekty.

    Powodem, dla którego problem pojawia się dopiero w czasie wykonywania aplikacji, a nie podczas budowania, jest to, że prisma generate — które uruchamia się na etapie budowania — w rzeczywistości nigdy nie uruchamia binarnika silnika zapytań. Tworzy on jedynie kod klienta w TypeScript. Sam binarnik silnika nie jest wywoływany, dopóki aplikacja się nie uruchomi i Prisma nie spróbuje nawiązać połączenia z bazą danych. W tym momencie znajdujesz się już w działającym kontenerze z niekompatybilną lub brakującą biblioteką OpenSSL, i nie ma już żadnych ostrzeżeń przed awarią.

    Rozwiązanie

    Rozwiązaniem jest wyraźne zainstalowanie OpenSSL w pliku Dockerfile za pomocą menedżera pakietów Alpine, apk. Który pakiet jest potrzebny, zależy wyłącznie od informacji zawartych w komunikacie o błędzie.

    Jeśli błąd wspomina o libssl.so.1.1 (OpenSSL 1.1):

    W nowszych wersjach Alpine (3.17 i wyższe) OpenSSL 1.1 nie jest już instalowany domyślnie, dlatego potrzebny jest pakiet kompatybilnościowy:

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

    Jeśli błąd wspomina o libssl.so.3 (OpenSSL 3) lub używasz najnowszej wersji Prismy na nowszej wersji Alpine:

    RUN apk add --no-cache openssl
    

    To ładuje wersję OpenSSL dostarczaną przez repozytorium pakietów Alpine dla danej wersji — w Alpine 3.17+ jest to OpenSSL 3.

    Najbezpieczniejszą strategią długoterminową jest celowe zabezpieczenie wersji oprogramowania. Popraw swoją wersję Alpine za pomocą tagu obrazu bazowego, a także ustal konkretną wersję Prisma w pliku package.json, aby wymagania dotyczące OpenSSL pozostały spójne i nie zmieniały się niespodziewanie w różnych środowiskach. W swoich notatkach o wydaniu Prisma podano, z jakiej wersji OpenSSL skompilowany został każdy plik binarny silnika.

    Jeśli twoja konfiguracja wykorzystuje budowę wieloetapową, musisz dodać tę linię do obu etapów, ponieważ każdy z nich działa niezależnie na 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"]
    

    Flaga --no-cache zapobiega przechowywaniu lokalnej kopii indeksu pakietów przez narzędzie apk, co sprawia, że rozmiar końcowego obrazu pozostaje mały — zgodnie z głównym powodem, dla którego wybrałeś Alpine od początku.

    Dlaczego nie po prostu przejść na node:slim?

    To słuszne pytanie. node:20-slim jest oparty na Debianie, a nie Alpine, przy czym Debian dostarcza OpenSSL od razu wraz z systemem, dzięki czemu ten konkretny problem praktycznie znika. Wiele zespołów po prostu dokonuje tej zmiany i kontynuuje pracę.

    Prymatem jest tutaj rozmiar obrazu. Typowy obraz Node oparty na Alpine ma rozmiar około 50 do 70 MB, podczas gdy odpowiednik w wersji slim dla Debiana osiąga rozmiar około 150 do 200 MB. Gdy pomnoży się tę różnicę przez dziesiątki implementacji, kilka usług oraz wielokrotne pobieranie obrazów przez wszystkich członków zespołu, efekt szybko się nagromadza. Alpine ma również mniejszą powierzchnię ataku od razu po instalacji, co jest istotnym czynnikiem w środowiskach wymagających wysokiego poziomu bezpieczeństwa.

    Zainstalowanie brakującego pakietu za pomocą apk add to powszechnie stosowana metoda, bezpośrednio opisana w przewodnikach Prisma, i można ją już znaleźć w niezliczonych plikach Dockerfile używanych w produkcji. Przejście na obraz oparty na Debianie, aby uniknąć problemów z dopasowaniem wersji, to całkowicie uzasadniona decyzja, ale nadal warto zrozumieć, dlaczego dochodzi do takich niezgodności, niezależnie od wybranego obrazu bazowego.

    Uwaga na temat budowy w kilku etapach

    Jeśli dwie linie FROM oraz składnia --from=builder w wcześniejszym pliku Dockerfile wydały się nieznane, oto krótkie wyjaśnienie motywów tego podejścia.

    Dockerfile z jednym etapem, który kompiluje TypeScript, ostatecznie zawiera w obrazie produkcyjnym sam kompilator TypeScript, ts-node, wszystkie zależności typu devDependency oraz surowe pliki źródłowe. Nic z tego nie powinno się tam znajdować — to powoduje przyrost masy obrazu i zwiększa powierzchnię narażoną na potencjalne zagrożenia.

    Budowa wieloetapowa zapewnia prawidłowe rozdzielenie elementów: etap budowania zawiera wszystko, co jest potrzebne do skompilowania projektu, natomiast etap produkcyjny rozpoczyna się od czystej bazy i pobiera tylko to, co jest faktycznie wymagane w czasie działania — skompilowane pliki z katalogu dist/, zależności przeznaczone wyłącznie do użycia w produkcji oraz wygenerowany Prisma Client. Wszystko to, co jest zbędne z etapu budowania, jest automatycznie pomijane po utworzeniu ostatecznego obrazu przez Docker.

    W rezultacie otrzymujemy lekki obraz gotowy do użycia w produkcji, który nie zawiera własnego zestawu narzędzi do budowy.

    Po naprawie

    Gdy zainstalowana zostanie odpowiednia wersja OpenSSL, uruchomienie docker compose up powinno w końcu dać oczekiwany wynik:

    ✅ Connected to PostgreSQL
    🚀 Server listening on port 4000
    

    Następnie, jeśli Twoja konfiguracja Prisma jest uruchamiana za pomocą Docker Compose, kolejnym krokiem jest zastosowanie migracji do bazy danych w formie kontenera, a nie do tej działającej lokalnie. Krótko mówiąc, oznacza to uruchomienie docker compose exec app npx prisma migrate deploy, co wykonuje polecenie migracji bezpośrednio wewnątrz działającego kontenera aplikacji — gdzie DATABASE_URL wskazuje na usługę Postgres zarządzaną przez Compose, a nie na jakikolwiek zasób znajdujący się na maszynie hosta.

    Błąd OpenSSL może przerażać przy pierwszym napotkaniu – to gęsty blok informacji binarnych kończący się wartością ENOENT. Jednak przyczyna jest konkretna i prosta: silnik zapytań Prisma to skompilowany plik binarny stworzony pod konkretną wersję OpenSSL, a system Alpine nie gwarantuje obecności tej wersji. Przeczytaj uważnie komunikat o błędzie, określ, jaką wersję libssl wymaga, zainstaluj ją odpowiednio i ustal konkretne wersje oprogramowania, aby zachowanie systemu pozostawało przewidywalne niezależnie od miejsca uruchomienia kontenera.