fix(docker): API image could never boot — missing workspace link and Prisma engine
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m44s
Build and Push Images / Build jorgecuadros-api (push) Successful in 1m57s

Two independent defects in docker/api.Dockerfile, both found by booting the
published image on galactus rather than by reading it. Neither had ever been
observed because no deploy had previously got far enough to start the API.

1. "Cannot find module '@jorgecuadros/database'".
   node-linker=hoisted flattens EXTERNAL dependencies into /repo/node_modules,
   but the workspace dependency stays linked per-package at
   apps/api/node_modules/@jorgecuadros/database -> ../../../../packages/database.
   The runtime stage copied only /repo/node_modules, so the link was dropped.
   Copy the @jorgecuadros scope dir as well — not the whole directory, whose
   only other contents are devDependencies.

2. "Prisma Client could not locate the Query Engine for runtime
   linux-musl-openssl-3.0.x ... generated for linux-musl".
   Prisma picks its engine by sniffing the build environment. The build stage
   had no openssl so it generated for plain "linux-musl", while the runtime
   stage demanded the openssl-3.0.x variant and refused to start. Fixed at both
   ends: binaryTargets now names the musl target explicitly in schema.prisma,
   so the shipped engine no longer depends on what happens to be installed at
   build time, and openssl is installed in the deps stage (generate) and the
   runtime stage (Prisma needs it regardless).

Verified by running the published image on galactus with each fix patched in
by hand, against the real database, until it got past both failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 14:39:25 -07:00
co-authored by Claude Opus 5
parent 27f04f1073
commit 3ff56e6b72
2 changed files with 24 additions and 2 deletions
+17 -2
View File
@@ -8,7 +8,9 @@ RUN corepack enable && corepack prepare pnpm@9.15.9 --activate
FROM base AS deps
# argon2's native addon has no musl prebuild -> compiles from source here.
RUN apk add --no-cache python3 make g++
# openssl so `prisma generate` in the build stage sees the same platform the
# runtime stage does (see the binaryTargets note in schema.prisma).
RUN apk add --no-cache python3 make g++ openssl
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/api/package.json apps/api/package.json
COPY apps/web/package.json apps/web/package.json
@@ -32,7 +34,11 @@ ENV NODE_ENV=production
# (mysqldump), restores (mysql), and the re-import pipeline (python + mdbtools)
# from inside the API container. Build deps are installed in a throwaway virtual
# package so pandas/pyarrow build on musl, then dropped from the final layer.
RUN apk add --no-cache python3 mdbtools mysql-client \
# openssl is NOT optional: Prisma's query engine resolves its binary target at
# runtime (linux-musl-openssl-3.0.x) and aborts with "Please manually install
# OpenSSL" without it. Node bundles its own OpenSSL, so nothing else in this
# image pulls the system package in.
RUN apk add --no-cache python3 mdbtools mysql-client openssl \
&& apk add --no-cache --virtual .pybuild python3-dev build-base \
&& rm -rf /var/cache/apk/*
@@ -40,6 +46,15 @@ COPY --from=build /repo/node_modules node_modules
COPY --from=build /repo/packages/database packages/database
COPY --from=build /repo/apps/api/dist apps/api/dist
COPY --from=build /repo/apps/api/package.json apps/api/package.json
# node-linker=hoisted flattens EXTERNAL deps into /repo/node_modules, but the
# workspace dependency is still linked per-package:
# apps/api/node_modules/@jorgecuadros/database -> ../../../../packages/database
# Copying only /repo/node_modules therefore drops it and the API dies at boot
# with "Cannot find module '@jorgecuadros/database'". Copy just the scope dir —
# the rest of apps/api/node_modules is devDependencies (typescript) we don't
# want in the runtime layer. The relative link resolves because packages/database
# is copied to the same place above.
COPY --from=build /repo/apps/api/node_modules/@jorgecuadros apps/api/node_modules/@jorgecuadros
# Migration scripts + their own Python venv (ops.service.ts prefers this venv).
COPY migration migration
+7
View File
@@ -8,6 +8,13 @@
generator client {
provider = "prisma-client-js"
output = "../generated/client"
// "native" covers local dev. The musl target is declared EXPLICITLY because
// Prisma picks the engine by sniffing the build environment: the Docker build
// stage has no openssl, so it detected plain "linux-musl", while the runtime
// stage (which needs openssl for other reasons) then demanded
// "linux-musl-openssl-3.0.x" and refused to start. Naming it here makes the
// engine that ships independent of what happens to be installed at build time.
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {