Home / Articles

Fixing Prisma's libssl.so.1.1 Missing Library Error on Alpine Docker

Prisma's query engine crashes on Alpine Docker images with a missing libssl.so.1.1. What's actually going on with OpenSSL and how to fix it for good in Node.js.

Publisher: REACTAPP.TOP6 min read1479 words

This article is published in English.

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

You wrote the Dockerfile, kicked off the build, and everything looked fine. Then you ran docker compose up, watched the log stream go by, and right at the moment the app should have connected to the database, it fell over.

Symptoms

If any of these look familiar, the explanation and fix are below:

  • A crash reporting that a shared library named libssl.so.1.1 couldn't be located anywhere on the filesystem
  • A Prisma startup failure showing PrismaClientInitializationError: Unable to require libquery_engine-linux-musl.so.node
  • A message stating that Prisma couldn't figure out which libssl/openssl version applied
  • The container started by docker compose up shutting down almost immediately
  • A build that finishes without any complaints, yet the container dies the instant it tries to talk to the database

The full error typically looks something like this:

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)

The Stack

The scenario where this surfaced involved a TypeScript-based Node.js/Express API, Prisma as the ORM layer, and PostgreSQL as the database. The Dockerfile was based on node:20-alpine, a reasonable default since Alpine-based images are considerably lighter than their Debian equivalents — faster to pull, smaller surface for vulnerabilities, and generally better suited to production deployments.

The build itself went through without issues. It was a multi-stage setup: TypeScript compiled down to dist/, and the Prisma Client got generated correctly. No errors during build. The trouble only appeared once docker compose up actually ran the container.

What's Actually Going On

Alpine Linux is deliberately stripped down. That's the entire appeal — it's why the resulting images are so small. But being minimal means Alpine leaves out a lot of packages that Debian- or Ubuntu-based images include by default.

Prisma's query engine ships as a precompiled binary — a .so (shared object) file bundled with your project, named something like libquery_engine-linux-musl.so.node in this context. This binary is responsible for the actual communication with your database, and it depends directly on OpenSSL to run at all.

What matters here is that this engine binary is linked against one particular build of OpenSSL, not OpenSSL as a general concept. Which version it expects depends on both your Prisma version and your Alpine release:

  • Earlier Prisma engine builds were linked against OpenSSL 1.1, which exposes libssl.so.1.1
  • More recent Prisma versions are built against OpenSSL 3, which exposes libssl.so.3
  • Alpine releases up through 3.16 shipped OpenSSL 1.1 as the default
  • Alpine 3.17 and later ship OpenSSL 3 instead

Because of that, the exact error you hit depends on which Prisma and Alpine versions you're combining. Read the error text closely — it names the specific shared library that's missing, and libssl.so.1.1 versus libssl.so.3 calls for a different package and a different fix.

The reason this doesn't show up until runtime, rather than failing the build, is that prisma generate — which executes during the build stage — never actually runs the query engine binary. It only produces the TypeScript client code. The engine binary itself doesn't get invoked until the application starts up and Prisma tries to open a database connection. At that point you're already inside a running container with an incompatible or absent OpenSSL library, and there's no more warning before things break.

The Fix

The solution is to explicitly install OpenSSL in your Dockerfile through Alpine's package manager, apk. Which package you need depends entirely on what the error message told you.

If the error mentions libssl.so.1.1 (OpenSSL 1.1):

On newer Alpine builds (3.17 and up), OpenSSL 1.1 isn't installed by default anymore, so you need the compatibility package:

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

If the error mentions libssl.so.3 (OpenSSL 3), or you're running a recent Prisma release on a recent Alpine version:

RUN apk add --no-cache openssl

This pulls in whichever OpenSSL build is provided by Alpine's package repository for that release — on Alpine 3.17+, that's OpenSSL 3.

The safest long-term strategy is to lock your versions down deliberately. Fix your Alpine version through the base image tag, and pin your Prisma version inside package.json, so the OpenSSL requirement stays consistent and doesn't shift unexpectedly across environments. Prisma's own release notes list which OpenSSL build each engine binary is compiled against.

If your setup uses a multi-stage build, you need to add this line to both stages, since each one runs on Alpine independently:

# 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"]

The --no-cache flag prevents apk from keeping a local copy of the package index, which keeps the final image size small — in keeping with the whole reason you picked Alpine to begin with.

Why Not Just Switch to node:slim?

That's a fair question to raise. node:20-slim is built on Debian rather than Alpine, and Debian ships OpenSSL out of the box, so this particular failure mode mostly disappears. Plenty of teams simply make that switch and move forward.

The catch is image size. A typical Alpine-based Node image lands somewhere around 50 to 70MB, while the Debian slim equivalent runs closer to 150 to 200MB. Multiply that gap across dozens of deployments, several services, and repeated pulls by everyone on the team, and it adds up quickly. Alpine also carries a smaller attack surface out of the box, which is a meaningful factor if you're operating in a security-sensitive setting.

Installing the missing package via apk add is a well-trodden path, documented directly in Prisma's guides, and you'll find it in countless production Dockerfiles already. Moving to a Debian-based image to sidestep the version-matching headache is a perfectly valid choice, but it's still worth understanding why the mismatch happens in the first place, no matter which base image you settle on.

A Note on Multi-Stage Builds

If the two FROM lines and the --from=builder syntax in the earlier Dockerfile felt unfamiliar, here's the reasoning behind that pattern in brief.

A Dockerfile with a single stage that compiles TypeScript ends up shipping the TypeScript compiler, ts-node, every devDependency, and your raw source files straight into the production image. None of that has any business being there — it bloats the image and widens the surface exposed to potential vulnerabilities.

A multi-stage build keeps things properly separated: the builder stage holds everything needed to compile the project, while the production stage starts from a clean base and only picks up what's actually required at runtime — the compiled dist/ output, production-only dependencies, and the generated Prisma Client. Everything bulky from the builder stage gets left behind automatically once Docker assembles the final image.

What you end up with is a slim, production-ready image that isn't dragging around its own build toolchain.

After the Fix

With the right OpenSSL version installed, running docker compose up should finally give you the output you were hoping for:

✅ Connected to PostgreSQL
🚀 Server listening on port 4000

From there, if your Prisma setup runs through Docker Compose, the next thing to sort out is applying migrations against the containerized database instead of whatever's running locally. In short, that means running docker compose exec app npx prisma migrate deploy, which executes the migration command from inside the running app container — where DATABASE_URL is pointed at your Compose-managed Postgres service rather than anything sitting on your host machine.

The OpenSSL error is intimidating the first time you hit it, a dense block of binary path output ending in ENOENT. But the root cause is narrow and specific: Prisma's query engine is a compiled binary built against a particular OpenSSL version, and Alpine offers no guarantee that version is actually present. Read the error message closely, figure out which libssl it's asking for, install it accordingly, and lock your versions down so the behavior stays predictable no matter where the container runs.