Inicio / Artículos

Solución del error de biblioteca faltante libssl.so.1.1 en Prisma en Alpine Docker

El motor de consultas de Prisma se cae en imágenes Docker Alpine cuando falta libssl.so.1.1. ¿Qué está sucediendo realmente con OpenSSL y cómo solucionarlo de forma permanente en Node.js?

Editor: REACTAPP.TOP6 min de lectura1479 palabras

Fuentes: Prisma Docker Error: `libssl.so.1.1` Not Found on Alpine

Escribió el Dockerfile, inició la compilación y todo parecía estar bien. Luego ejecutó docker compose up, observó cómo pasaba el flujo de registros y, justo en el momento en que la aplicación debería haberse conectado a la base de datos, falló.

Síntomas

Si alguno de estos le resulta familiar, la explicación y la solución se encuentran a continuación:

  • Un informe de fallo que indica que no se pudo localizar en ningún lugar del sistema de archivos una biblioteca compartida llamada libssl.so.1.1
  • Un error al iniciar Prisma que muestra PrismaClientInitializationError: Unable to require libquery_engine-linux-musl.so.node
  • Un mensaje que indica que Prisma no pudo determinar qué versión de libssl/openssl se está utilizando
  • El contenedor iniciado con docker compose up se cierra casi de inmediato
  • Una compilación que finaliza sin ningún problema, pero el contenedor falla en el instante en que intenta comunicarse con la base de datos
  • El error completo suele verse más o menos así:

    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)
    

    La pila de ejecución

    El escenario en el que surgió este problema involucraba una API de Node.js/Express basada en TypeScript, Prisma como capa ORM y PostgreSQL como base de datos. El Dockerfile se basaba en node:20-alpine, una opción por defecto razonable, ya que las imágenes basadas en Alpine son considerablemente más ligeras que sus equivalentes de Debian: se descargan más rápido, tienen menos superficie para vulnerabilidades y son en general más adecuadas para despliegues en producción.

    La compilación en sí se realizó sin problemas. Fue un proceso en varias etapas: TypeScript se compiló hasta dist/, y el Prisma Client se generó correctamente. No hubo errores durante la compilación. Los problemas solo aparecieron cuando docker compose up ejecutó realmente el contenedor.

    ¿Qué está sucediendo realmente?

    Alpine Linux está diseñado de forma intencional para ser muy ligero. Ese es todo su atractivo: por eso las imágenes resultantes son tan pequeñas. Pero al ser mínimo, Alpine omite muchos paquetes que las imágenes basadas en Debian o Ubuntu incluyen por defecto.

    El motor de consultas de Prisma se entrega como un binario precompilado: un archivo .so (objeto compartido) incluido en tu proyecto, denominado algo como libquery_engine-linux-musl.so.node en este contexto. Este binario es el responsable de la comunicación real con tu base de datos, y depende directamente de OpenSSL para poder ejecutarse.

    Lo importante aquí es que este binario del motor está vinculado a una versión *específica* de OpenSSL, no a OpenSSL en general. La versión que espera depende tanto de la versión de Prisma como de la versión de Alpine:

    • Las versiones anteriores del motor de Prisma estaban vinculadas a OpenSSL 1.1, que expone libssl.so.1.1
    • Las versiones más recientes de Prisma están desarrolladas con OpenSSL 3, que expone libssl.so.3
    • Las versiones de Alpine hasta 3.16 ven OpenSSL 1.1 como predeterminado
  • Alpine 3.17 y versiones posteriores incluyen OpenSSL 3 en su lugar
  • Por eso, el error específico que se presenta depende de las versiones de Prisma y Alpine que se combinen. Lea atentamente el texto del error: indica la biblioteca compartida concreta que falta, y libssl.so.1.1 frente a libssl.so.3 requieren un paquete distinto y una solución diferente.

    La razón por la que esto no se detecta hasta el tiempo de ejecución, en lugar de causar un fallo durante la compilación, es que prisma generate, que se ejecuta en la fase de compilación, nunca ejecuta realmente el binario del motor de consultas. Solo genera el código cliente en TypeScript. El propio binario del motor no se invoca hasta que la aplicación se inicia y Prisma intenta establecer una conexión a la base de datos. Para entonces ya se está dentro de un contenedor en ejecución con una biblioteca OpenSSL incompatible o ausente, y no hay más advertencias antes de que surjan los problemas.

    La solución

    La solución consiste en instalar explícitamente OpenSSL en su Dockerfile a través del gestor de paquetes de Alpine, apk. El paquete que necesite depende por completo de lo que le indique el mensaje de error.

    Si el error menciona libssl.so.1.1 (OpenSSL 1.1):

    En las versiones más recientes de Alpine (3.17 en adelante), OpenSSL 1.1 ya no se instala por defecto, por lo que necesita el paquete de compatibilidad:

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

    Si el error menciona libssl.so.3 (OpenSSL 3), o si está ejecutando una versión reciente de Prisma en una versión reciente de Alpine:

    RUN apk add --no-cache openssl
    

    Esto carga la versión de OpenSSL que proporciona el repositorio de paquetes de Alpine para esa versión; en Alpine 3.17 y superiores, se trata de OpenSSL 3.

    La estrategia a largo plazo más segura es fijar deliberadamente sus versiones. Corrija su versión de Alpine a través de la etiqueta de imagen base y fije su versión de Prisma dentro de package.json, para que el requisito de OpenSSL se mantenga consistente y no cambie inesperadamente entre entornos. Las notas de lanzamiento de Prisma mismas indican contra qué versión de OpenSSL se compila cada binario del motor.

    Si su configuración utiliza una compilación en múltiples etapas, debe agregar esta línea a ambas etapas, ya que cada una se ejecuta de forma independiente en 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"]
    

    La bandera --no-cache impide que apk guarde una copia local del índice de paquetes, lo que mantiene el tamaño final de la imagen pequeño, en línea con la razón principal por la que eligió Alpine desde un principio.

    ¿Por qué no simplemente cambiar a node:slim?

    Esa es una pregunta válida. node:20-slim está basado en Debian y no en Alpine, y Debian incluye OpenSSL de forma predeterminada, por lo que este modo específico de fallo desaparece en gran medida. Muchos equipos simplemente realizan ese cambio y siguen adelante.

    El problema radica en el tamaño de las imágenes. Una imagen típica de Node basada en Alpine tiene un tamaño de entre 50 y 70 MB, mientras que la versión equivalente de Debian slim alcanza entre 150 y 200 MB. Si se multiplica esa diferencia en decenas de despliegues, varios servicios y las descargas repetidas por todos los miembros del equipo, el volumen total aumenta rápidamente. Además, Alpine cuenta con una superficie de ataque más reducida por defecto, lo cual es un factor importante si se trabaja en un entorno sensible desde el punto de vista de la seguridad.

    Instalar el paquete faltante mediante apk add es un método muy utilizado, documentado directamente en las guías de Prisma, y ya se puede encontrar en innumerables Dockerfiles de producción. Pasar a una imagen basada en Debian para evitar los problemas relacionados con la compatibilidad de versiones es una opción perfectamente válida, pero sigue siendo útil comprender por qué ocurre esa incompatibilidad en primer lugar, independientemente de la imagen base que se elija.

    Una nota sobre las construcciones multietapa

    Si las dos líneas FROM y la sintaxis --from=builder del Dockerfile anterior le resultaron desconocidas, aquí hay una breve explicación de la lógica detrás de ese patrón.

    Un Dockerfile con una sola etapa que compila TypeScript termina incluyendo el compilador de TypeScript, ts-node, todas las dependencias de desarrollo y tus archivos de código fuente directamente en la imagen de producción. Nada de eso debería estar allí, ya que hace que la imagen sea más grande y aumenta las posibilidades de exposición a vulnerabilidades.

    Una construcción multietapa mantiene todo bien separado: la etapa de compilación contiene todo lo necesario para compilar el proyecto, mientras que la etapa de producción parte de una base limpia y solo incluye lo que realmente se necesita en tiempo de ejecución: los archivos compilados en dist/, las dependencias exclusivas para producción y el Cliente Prisma generado. Todo lo voluminoso de la etapa de compilación se deja atrás automáticamente una vez que Docker crea la imagen final.

    Lo que obtienes al final es una imagen ligera y lista para producción que no lleva consigo su propia cadena de herramientas de compilación.

    Después de la corrección

    Con la versión correcta de OpenSSL instalada, ejecutar docker compose up debería finalmente proporcionarle el resultado que esperaba:

    ✅ Connected to PostgreSQL
    🚀 Server listening on port 4000
    

    A partir de ahí, si su configuración de Prisma se ejecuta a través de Docker Compose, lo siguiente que hay que resolver es aplicar las migraciones en la base de datos contenerizada en lugar de en lo que esté ejecutándose localmente. En resumen, eso significa ejecutar docker compose exec app npx prisma migrate deploy, lo cual ejecuta la orden de migración desde dentro del contenedor de la aplicación en ejecución, donde DATABASE_URL apunta al servicio Postgres gestionado por Compose y no a cualquier otro servicio en su máquina host.

    El error de OpenSSL puede resultar intimidante la primera vez que lo encuentras: un bloque denso de salida en formato binario que termina con ENOENT. Pero la causa raíz es específica y sencilla: el motor de consultas de Prisma es un binario compilado para una versión concreta de OpenSSL, y Alpine no garantiza que esa versión esté realmente presente. Lee detenidamente el mensaje de error, determina qué libssl está solicitando, instálalo en consecuencia y establece versiones fijas para que el comportamiento sea predecible sin importar dónde se ejecute el contenedor.

    Lecturas relacionadas

  • Desarrollo local de Azure Functions: Arreglando los puntos comunes de fallo — Aprenda cómo deben alinearse las Herramientas Básicas, los entornos de ejecución del lenguaje y Azurite, y obtenga soluciones prácticas para problemas en local.settings.json, desencadenadores y errores de depuración.
  • Diagnosticando fallos en Prisma Guard: Un modelo diagnóstico basado en fases — Aprenda a diagnosticar fallos en la API generada por Prisma asignando los errores a la fase específica —configuración, selección del llamante, validación o respuesta— que los causa.