Files
vas3k-TaxHacker/Dockerfile
Denis Pisarev 69fc82f7d0 build(docker): fix Prisma client generation for multi-arch builds (#115)
* chore(deps): bump the prisma group to 7.8.0

Bumps @prisma/client and prisma from ^6.6.0 to ^7.8.0 (major).
Ports dependabot bump eede1a7 onto the reconstructed base.

* fix: migrate to Prisma 7 config format

Prisma 7 removed url from the datasource block in schema.prisma.
- Move connection URL to prisma.config.ts for CLI (migrations)
- Use @prisma/adapter-pg for runtime client connections
- Update lib/db.ts to create PrismaClient with adapter

* fix: split defaults to break client-side Prisma adapter import chain

setup-form-client.tsx (client component) imported constants from
models/defaults.ts which had a top-level import of lib/db.ts, pulling
@prisma/adapter-pg (with Node.js builtins) into the client bundle.

Split into defaults-data.ts (pure constants, no imports) and
defaults.ts (re-exports + server functions with prisma).

* fix: exclude @prisma/adapter-pg from Turbopack bundling

The adapter imports Node.js builtins (dns, net, tls, fs) that Turbopack
cannot resolve for browser bundles. Add to serverExternalPackages.

Dockerfile deps-stage devDependencies change from the original commit is
deferred to the later Docker task: this base has the pre-refactor
Dockerfile (no deps stage) that the Docker multi-arch task reworks.

* fix: complete Prisma 7 migration with prisma-client generator

Switch generator to prisma-client (Rust-free) so generation no longer
depends on the native query engine. The legacy prisma-client-js generator
invokes getDmmf via the engine binary, which corrupts the DMMF JSON under
QEMU cross-emulation on the linux/arm64 platform build.

Add tsconfig path alias mapping @/prisma/client to the new client entry
(./prisma/client/client). The new generator emits TypeScript source with
no index file, so the existing imports resolve through the alias without
modification.

Deferred to the later Docker task: copying prisma.config.ts into the
runtime image (the Dockerfile in this base predates the multi-stage
refactor that the Docker task introduces).

* ci: bump Node to 26 in ci.yml to satisfy Prisma 7's engine requirement

Prisma 7.8.0's preinstall check requires Node 20.19+, 22.12+, or 24.0+.
ci.yml still pinned actions/setup-node to "23" (an odd-numbered, non-LTS
release outside all three supported ranges), so npm ci failed in CI once
this branch's Prisma bump landed. Bumps to "26" to match the Dockerfile's
existing node:26-slim base image.

* ci,docker: generate Prisma client before typecheck/build

Neither ci.yml's jobs nor the Dockerfile ran  before
this branch's Prisma 7 migration, which is why Type Check and Docker
Build both failed with "Cannot find module '@/prisma/client'": npm ci's
postinstall doesn't run in this environment, and even where it does,
Prisma 7's postinstall needs prisma.config.ts, which isn't present until
after Dockerfile's COPY . . step.

Corrects the earlier ci.yml Node bump to "24" (matching this repo's real
already-migrated main) instead of "26" — the Dockerfile's node:26-slim
base image is a separate, intentionally different pin.

* fix(docker): regenerate prisma client after COPY to prevent stale binary

COPY . . overwrites the prisma generate output from npm ci postinstall
with stale local files, causing 'Engine is not yet connected' at runtime.

* fix: run prisma generate on native build platform

Under QEMU arm64 emulation, Prisma's get-dmmf step produces invalid
output: the legacy native engine corrupted the DMMF JSON, and the v7
WASM engine reports false schema validation errors (P1012) on valid
fields. The schema is correct, as proven by the amd64 build succeeding.

Pin the codegen stage to --platform=$BUILDPLATFORM so prisma generate
always runs on the native amd64 host, never under emulation. The
generated client is platform-independent TypeScript (no native
binaries), so the arm64 and amd64 builder stages consume the same
output via COPY --from=codegen.

Also set DEBIAN_FRONTEND=noninteractive in the base stage to silence
debconf frontend fallback warnings during apt-get install.

* fix: pre-generate Prisma client in CI to avoid QEMU corruption

The --platform=$BUILDPLATFORM codegen stage did not prevent buildx from
running prisma generate under QEMU for the arm64 target, so get-dmmf
still corrupted its output (31 validation errors on a valid schema).

Move prisma generate out of the emulated build entirely: the workflow
runs npm ci + npx prisma generate on the native runner before buildx,
and passes SKIP_PRISMA_GENERATE=true so the Dockerfile skips its
in-Docker generate. The generated client is platform-independent
TypeScript, included in the build context (removed from .dockerignore).

Local single-platform builds are unaffected: SKIP_PRISMA_GENERATE
defaults to false, so prisma generate runs inside Docker natively.

* fix: strip empty-string env vars before zod parsing

config.ts now strips empty-string env values before zod parsing, so
.default() applies when a variable is set to empty (e.g. Portainer
passing BETTER_AUTH_SECRET=""). Previously the empty string bypassed the
default and failed .min(16), crashing the app with a 500 before any logs.

* fix(docker): restore NODE_ENV=production before next build

npm ci needs NODE_ENV=development to install devDependencies (the
prisma CLI used by prisma generate), but next build treats a
non-production NODE_ENV as a dev build and fails static export of
/_error and /404 with "<Html> should not be imported outside of
pages/_document". Reset NODE_ENV=production immediately after npm ci;
already-installed devDependencies stay on disk regardless of the env
var.

Also incorporate the two Dockerfile changes deferred from the Prisma 7
migration: install devDependencies in the builder stage that runs
prisma generate, and copy prisma.config.ts into the runtime image so
prisma migrate deploy (run by docker-entrypoint.sh) can find the
datasource URL.

* fix(docker): restore SKIP_PRISMA_GENERATE guard dropped by merge conflict resolution

The merge of main (#114) into this branch resolved a Dockerfile conflict
by taking the unconditional `RUN npx prisma generate` from #114, silently
dropping the ARG SKIP_PRISMA_GENERATE guard this branch adds. The
docker-latest.yml/docker-release.yml workflows still pass
SKIP_PRISMA_GENERATE=true as a build-arg, but with the ARG undeclared the
Dockerfile ignored it and ran prisma generate unconditionally — including
under QEMU emulation for arm64, the exact corruption this branch exists
to prevent. Restores the guard.

---------

Co-authored-by: Vasily Zubarev <me@vas3k.ru>
2026-07-06 15:44:52 +02:00

86 lines
2.3 KiB
Docker

FROM node:26-slim AS base
# Default environment variables
ENV PORT=7331
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV DEBIAN_FRONTEND=noninteractive
# Build stage
FROM base AS builder
# Install dependencies required for Prisma
RUN apt-get update && apt-get install -y openssl
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY prisma ./prisma/
# Install dependencies, including devDependencies (the `prisma` CLI needed
# below by `prisma generate` is a devDependency; production stage never sees
# this override since it starts fresh `FROM base`)
ENV NODE_ENV=development
RUN npm ci
# Restore NODE_ENV=production for the rest of this stage: `next build` treats
# a non-production NODE_ENV as a dev build, which breaks static export of
# /_error and /404 ("<Html> should not be imported outside of pages/_document").
# The already-installed devDependencies (e.g. the `prisma` CLI) stay on disk
# regardless of this env var.
ENV NODE_ENV=production
# Copy source code
COPY prisma.config.ts ./
COPY . .
# CI pre-generates prisma/client natively (see workflows) and sets
# SKIP_PRISMA_GENERATE=true so get-dmmf never runs under QEMU emulation.
# Local single-platform builds run prisma generate here (natively, safe).
ARG SKIP_PRISMA_GENERATE=false
RUN if [ "${SKIP_PRISMA_GENERATE}" != "true" ]; then npx prisma generate; fi
# Build the application
RUN npm run build
# Production stage
FROM base
# Install required system dependencies
RUN apt-get update && apt-get install -y \
ca-certificates \
ghostscript \
graphicsmagick \
openssl \
libwebp-dev \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Create upload directory and set permissions
RUN mkdir -p /app/upload
# Copy built assets from builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/prisma.config.ts ./
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/app ./app
COPY --from=builder /app/next.config.ts ./
# Copy and set up entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Create directory for uploads
RUN mkdir -p /app/data
EXPOSE 7331
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["npm", "start"]