Résolution de l’erreur de bibliothèque manquante libssl.so.1.1 dans Prisma sur Alpine Docker
Le moteur de requêtes de Prisma plante sur les images Docker Alpine en l’absence de libssl.so.1.1. Que se passe-t-il réellement avec OpenSSL et comment le résoudre définitivement dans Node.js ?
Sources: Prisma Docker Error: `libssl.so.1.1` Not Found on Alpine
docker compose up, suivi le flux des journaux, et au moment précis où l’application aurait dû se connecter à la base de données, elle est tombée en panne.
Symptômes
Si l’un de ces éléments vous semble familier, l’explication et la solution se trouvent ci-dessous :
- Un rapport de panne indiquant qu’une bibliothèque partagée nommée
libssl.so.1.1ne pouvait être trouvée nulle part dans le système de fichiers - Une erreur de démarrage de Prisma affichant
PrismaClientInitializationError: Unable to require libquery_engine-linux-musl.so.node - Un message indiquant que Prisma ne parvenait pas à déterminer quelle version de libssl/openssl était utilisée
- Le conteneur lancé par
docker compose ups’arrêtant presque immédiatement
L’erreur complète ressemble généralement à ceci :
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)
Le contexte
Celui dans lequel ce problème est apparu concernait une API Node.js/Express basée sur TypeScript, Prisma en tant que couche ORM, et PostgreSQL comme base de données. Le Dockerfile était basé sur node:20-alpine, un choix par défaut raisonnable, car les images basées sur Alpine sont considérablement plus légères que leurs équivalents Debian — elles se téléchargent plus rapidement, offrent moins de surfaces vulnérables et conviennent mieux aux déploiements en production.
La compilation s’est déroulée sans aucun problème. Il s’agissait d’une configuration en plusieurs étapes : TypeScript a été compilé vers dist/, et le Prisma Client a été généré correctement. Aucune erreur pendant la compilation. Les problèmes n’apparaissent que lorsque docker compose up lance réellement le conteneur.
Qu’est-ce qui se passe vraiment
Alpine Linux est délibérément simplifié. C’est là tout son attrait — c’est pourquoi les images générées sont si petites. Mais ce minimalisme signifie qu’Alpine omet de nombreux paquets que les images basées sur Debian ou Ubuntu incluent par défaut.
Le moteur de requêtes de Prisma est fourni sous forme de binaire précompilé — un fichier .so (objet partagé) inclus dans votre projet, portant dans ce cas un nom comme libquery_engine-linux-musl.so.node. Ce binaire est chargé de la communication réelle avec votre base de données, et il dépend directement d’OpenSSL pour fonctionner.
Ce qui est important ici, c’est que ce binaire du moteur est lié à une version spécifique d’OpenSSL, et non à OpenSSL en tant que concept général. La version qu’il attend dépend à la fois de la version de Prisma et de la version d’Alpine :
- Les versions anciennes du moteur de Prisma étaient liées à OpenSSL 1.1, qui expose
libssl.so.1.1 - Les versions plus récentes de Prisma sont construites avec OpenSSL 3, qui expose
libssl.so.3 - Les versions d’Alpine jusqu’à 3.16 utilisaient par défaut OpenSSL 1.1
Par conséquent, l’erreur précise que vous rencontrez dépend des versions de Prisma et d’Alpine que vous combinez. Lisez attentivement le texte de l’erreur : il indique la bibliothèque partagée spécifique qui manque, et libssl.so.1.1 versus libssl.so.3 nécessitent un paquet différent ainsi qu’une solution distincte.
Le fait que ce problème ne se manifeste qu’en temps de exécution, plutôt qu’au stade de la compilation, s’explique par le fait que prisma generate — qui s’exécute pendant la phase de compilation — ne lance jamais réellement le binaire du moteur de requêtes. Il se contente de générer le code client en TypeScript. Le binaire du moteur lui-même n’est invoqué qu’une fois l’application démarrée et que Prisma tente d’établir une connexion à la base de données. À ce moment-là, vous êtes déjà à l’intérieur d’un conteneur en cours d’exécution disposant d’une bibliothèque OpenSSL incompatible ou absente, et il n’y a plus d’avertissement avant que les problèmes ne surviennent.
La solution
La solution consiste à installer explicitement OpenSSL dans votre Dockerfile via le gestionnaire de paquets d’Alpine, apk. Le paquet nécessaire dépend entièrement du message d’erreur que vous avez reçu.
Si l’erreur mentionne libssl.so.1.1 (OpenSSL 1.1) :
Dans les versions plus récentes d’Alpine (3.17 et ultérieures), OpenSSL 1.1 n’est plus installé par défaut, il vous faut donc le paquet de compatibilité :
RUN apk add --no-cache openssl1.1-compat
Si l’erreur mentionne libssl.so.3 (OpenSSL 3), ou si vous exécutez une version récente de Prisma sur une version récente d’Alpine :
RUN apk add --no-cache openssl
Cela télécharge la version d’OpenSSL fournie par le répertoire de paquets d’Alpine pour cette version — sur Alpine 3.17+, il s’agit d’OpenSSL 3.
La stratégie à long terme la plus sûre consiste à verrouiller délibérément vos versions. Corrigez votre version d’Alpine à l’aide de l’attribut d’image de base, et fixez votre version Prisma à l’intérieur de package.json, afin que les exigences concernant OpenSSL restent constantes et ne changent pas de manière inattendue d’un environnement à l’autre. Les notes de version de Prisma indiquent quelles versions d’OpenSSL sont utilisées pour compiler chaque binaire du moteur.
Si votre configuration utilise une construction en plusieurs étapes, vous devez ajouter cette ligne dans les deux étapes, car chacune d’elles s’exécute indépendamment sur 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"]
Le flag --no-cache empêche apk de conserver une copie locale de l’index des paquets, ce qui maintient la taille finale de l’image faible — conformément à la raison même pour laquelle vous avez choisi Alpine en premier lieu.
Pourquoi ne pas simplement passer à node:slim ?
C’est une question légitime. node:20-slim est basé sur Debian plutôt que sur Alpine, et Debian fournit OpenSSL par défaut, ce qui fait disparaître en grande partie ce mode de défaillance particulier. De nombreuses équipes effectuent simplement ce changement et poursuivent leur travail.
Le problème réside dans la taille des images. Une image Node basée sur Alpine pèse généralement entre 50 et 70 MB, tandis que l’équivalent slim basé sur Debian atteint plutôt 150 à 200 MB. Si l’on multiplie cette différence par des dizaines de déploiements, plusieurs services, ainsi que les téléchargements répétés par tous les membres de l’équipe, cela représente rapidement une charge importante. De plus, Alpine dispose par défaut d’une surface d’attaque plus réduite, ce qui est un facteur important si l’on travaille dans un environnement sensible en termes de sécurité.
Installer le paquet manquant à l’aide de apk add est une méthode couramment utilisée, directement documentée dans les guides de Prisma, et on la trouve déjà dans d’innombrables Dockerfiles en production. Passer à une image basée sur Debian pour éviter les problèmes de compatibilité de version est un choix tout à fait valable, mais il reste utile de comprendre pourquoi ces incompatibilités surviennent en premier lieu, quel que soit l’image de base choisie.
Une note sur les builds multi-étapes
Si les deux lignes FROM ainsi que la syntaxe --from=builder présentes dans le Dockerfile précédent vous semblaient inhabituelles, voici en bref les raisons de ce schéma.
ts-node, toutes les dépendances de développement ainsi que vos fichiers sources bruts directement dans l’image de production. Rien de tout cela n’a sa place là — cela alourdit l’image et augmente la surface exposée à des vulnérabilités potentielles.
dist/, les dépendances réservées à la production et le client Prisma généré. Tout ce qui est encombrant provenant de l’étape de compilation est automatiquement laissé derrière une fois que Docker assemble l’image finale.
Après la correction
Avec la bonne version d’OpenSSL installée, l’exécution de docker compose up devrait enfin vous fournir le résultat escompté :
✅ Connected to PostgreSQL
🚀 Server listening on port 4000
Par la suite, si votre configuration Prisma s’exécute via Docker Compose, ce qu’il reste à régler est d’appliquer les migrations sur la base de données containerisée plutôt que sur celle qui tourne localement. En bref, il s’agit d’exécuter docker compose exec app npx prisma migrate deploy, ce qui lance la commande de migration depuis l’intérieur du conteneur de l’application en cours d’exécution — où DATABASE_URL fait référence au service Postgres géré par Compose et non à une base située sur votre machine hôte.
L’erreur OpenSSL peut effrayer la première fois qu’on la rencontre, avec un bloc dense d’informations binaires se terminant par ENOENT. Mais la cause racine est précise et limitée : le moteur de requêtes de Prisma est un binaire compilé pour une version spécifique d’OpenSSL, et Alpine ne garantit pas que cette version soit bien présente. Lisez attentivement le message d’erreur, déterminez quelle version de libssl est demandée, installez-la en conséquence, et fixez vos versions afin que le comportement reste prévisible quel que soit l’endroit où le conteneur s’exécute.