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>
85 lines
3.9 KiB
Docker
85 lines
3.9 KiB
Docker
FROM node:20-alpine AS base
|
|
WORKDIR /repo
|
|
# Pin pnpm 9 to match pnpm-lock.yaml (lockfileVersion 9.0). pnpm 9 runs
|
|
# dependency build scripts automatically (the v10 build-allowlist gating does
|
|
# not apply), so argon2's native addon + prisma engines build without extra
|
|
# approval config.
|
|
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.
|
|
# 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
|
|
COPY packages/database/package.json packages/database/package.json
|
|
# node-linker=hoisted flattens the store into a single npm-style /repo/node_modules
|
|
# so the runtime stage can copy one tree (pnpm's default symlinked layout would
|
|
# break across COPY stages).
|
|
RUN pnpm install --frozen-lockfile --config.node-linker=hoisted
|
|
|
|
FROM deps AS build
|
|
COPY packages/database packages/database
|
|
COPY apps/api apps/api
|
|
RUN pnpm --filter @jorgecuadros/database generate
|
|
RUN pnpm --filter @jorgecuadros/api build
|
|
|
|
FROM node:20-alpine AS runtime
|
|
WORKDIR /repo
|
|
ENV NODE_ENV=production
|
|
|
|
# DB-ops toolchain baked in so the "Operaciones" admin panel can run backups
|
|
# (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.
|
|
# 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/*
|
|
|
|
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
|
|
RUN python3 -m venv migration/.venv \
|
|
&& migration/.venv/bin/pip install --no-cache-dir -r migration/requirements.txt \
|
|
&& apk del .pybuild
|
|
|
|
# Ingest (uploaded Access files) and backups live on mounted volumes.
|
|
ENV MIGRATION_DIR=/repo/migration \
|
|
INGEST_DIR=/data/ingest \
|
|
BACKUP_DIR=/data/backups \
|
|
MIGRATION_ENV=dev
|
|
RUN mkdir -p /data/ingest /data/backups
|
|
|
|
# Build/version metadata baked in at image build time (see .gitea/workflows/build.yml).
|
|
# APP_VERSION is the metadata-action primary tag (semver tag, branch, or sha);
|
|
# GIT_SHA/BUILD_DATE pin the exact commit + build instant. Exposed as ENV so a
|
|
# running container can self-report what is deployed (e.g. a /version endpoint).
|
|
ARG APP_VERSION=dev
|
|
ARG GIT_SHA=unknown
|
|
ARG BUILD_DATE=unknown
|
|
ENV APP_VERSION=$APP_VERSION \
|
|
GIT_SHA=$GIT_SHA \
|
|
BUILD_DATE=$BUILD_DATE
|
|
|
|
EXPOSE 3001
|
|
CMD ["node", "apps/api/dist/main.js"]
|