FROM node:22-alpine AS base # FFmpeg for video transcoding (HLS), thumbnail extraction, and metadata RUN apk add --no-cache ffmpeg # Install dependencies only when needed FROM base AS deps WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci --ignore-scripts RUN npx prisma generate || true # Rebuild source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Generate Prisma client RUN npx prisma generate # Build the Next.js app RUN npm run build # Pre-compile the seed to a self-contained ESM bundle. Runtime then runs # plain `node prisma/seed-dow.mjs` and avoids the whole tsx+module- # resolution nightmare: Next.js's standalone node_modules was aggressively # reshuffling the tree after install, leaving packages like postgres-array # with a manifest but no index.js. Bundling sidesteps that entirely. # # ESM (not CJS) because Prisma's generated client uses `import.meta.url` # for engine detection — that's `undefined` in CJS and crashes on load. # # `--packages=external` keeps NPM packages (bcryptjs, @prisma/*) as # runtime import() calls so the bundle stays small and native .node files # in @prisma/client work normally. RUN npx -y esbuild@0.24 prisma/seed-dow.ts \ --bundle --platform=node --format=esm --target=node22 \ --outfile=prisma/seed-dow.mjs \ --packages=external # Production image FROM base AS runner WORKDIR /app ENV NODE_ENV=production RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public # Copy Next.js standalone output COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Copy Prisma schema, config, and migrations for runtime COPY --from=builder /app/prisma ./prisma COPY --from=builder /app/prisma.config.ts ./prisma.config.ts COPY --from=builder /app/src/generated ./src/generated # Runtime deps — the Next.js standalone bundle covers the app itself; # Runtime deps. The Next.js standalone tree ships with PARTIAL copies of # the pg driver chain — package.json manifests present but index.js # missing. npm's "version already satisfies, skipping" logic then fires # on every subsequent install attempt (including --force), because the # manifest version check passes and npm never looks at file integrity. # # Nuke the whole pg chain explicitly before reinstalling. Fail-fast at # the end so if it ever silently regresses we see FATAL in the build # log, not four hours later at seed time. RUN rm -rf \ node_modules/@prisma/adapter-pg \ node_modules/pg \ node_modules/pg-cloudflare \ node_modules/pg-connection-string \ node_modules/pg-int8 \ node_modules/pg-numeric \ node_modules/pg-pool \ node_modules/pg-protocol \ node_modules/pg-types \ node_modules/postgres-array \ node_modules/postgres-bytea \ node_modules/postgres-date \ node_modules/postgres-interval \ && npm install --no-save \ prisma@7.4.2 \ dotenv@17.3.1 \ @prisma/adapter-pg@7.4.2 \ pg@8 \ bcryptjs@3 \ && test -f node_modules/postgres-array/index.js \ || (echo "FATAL: postgres-array/index.js still missing after reinstall" >&2 && exit 1) # Create uploads directories for media storage RUN mkdir -p /data/uploads && chown nextjs:nodejs /data/uploads RUN mkdir -p /app/public/uploads/revisions && chown -R nextjs:nodejs /app/public/uploads USER nextjs EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" CMD ["sh", "-c", "./node_modules/.bin/prisma migrate deploy && node server.js"]