# syntax=docker/dockerfile:1.7 # # Production image for MEGA INFRASTRUCTURE — designed for Coolify. # # Multi-stage build: # 1. deps — fetch and cache npm dependencies # 2. builder — compile the Next.js standalone bundle (postinstall # automatically patches Next 16's dev-only cross-site # guard so we can host under any domain / IP) # 3. runner — minimal runtime image with just the standalone server # # The container listens on ${PORT:-3000} and binds to 0.0.0.0 so # Coolify's Traefik proxy can reach it. The /api/health endpoint is # used as the container healthcheck. # --------------------------------------------------------------------------- # 1. deps # --------------------------------------------------------------------------- FROM node:20-alpine AS deps RUN apk add --no-cache libc6-compat WORKDIR /app # Copy only the lockfile + manifest so this layer is cached as long # as dependencies don't change. COPY package.json package-lock.json* ./ # --ignore-scripts: the `postinstall` hook runs `scripts/patch-next-dev-blocker.cjs` # which targets Next 16's dev-only cross-site guard. That module is never # loaded in production, so the patch is unnecessary here. Skipping the # hook also decouples the install from the rest of the workspace — the # `scripts/` directory isn't copied until the next stage, but the hook # would otherwise fail with `MODULE_NOT_FOUND`. RUN npm ci --ignore-scripts --no-audit --no-fund # --------------------------------------------------------------------------- # 2. builder # --------------------------------------------------------------------------- FROM node:20-alpine AS builder # poppler-utils provides pdfinfo + pdftoppm, used by # src/lib/dn-pipeline/split.ts to rasterise delivery note pages. The # MiniMax OpenAI-compatible endpoint does not accept PDF documents, so # pages must become images before extraction. RUN apk add --no-cache poppler-utils WORKDIR /app # Pull in the prebuilt node_modules from the deps stage. COPY --from=deps /app/node_modules ./node_modules # Copy the rest of the source. Order matters: changing source code # invalidates this layer, but the layer above (node_modules) is kept. COPY . . # Opt out of Next.js telemetry during the build. ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production # Build the standalone server. This produces `.next/standalone/` which # already contains the trimmed `node_modules` needed at runtime plus a # ready-to-run `server.js`. RUN npm run build # --------------------------------------------------------------------------- # 3. runner # --------------------------------------------------------------------------- FROM node:20-alpine AS runner # Same reason as the builder stage — the running server shells out to # pdftoppm on every batch. RUN apk add --no-cache poppler-utils WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 # Create a non-root user to run the app. RUN addgroup --system --gid 1001 nodejs \ && adduser --system --uid 1001 nextjs # Static assets served directly by Next.js — must be copied alongside # the standalone output. COPY --from=builder /app/public ./public # The standalone output already includes a trimmed node_modules and the # Node executable wrapper. Static chunks live in `.next/static` and are # loaded by the standalone server at runtime — they must be copied in # separately because `output: "standalone"` does NOT bundle them. COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 # Coolify / Traefik can hit /api/health for a 200 OK liveness probe. HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD wget -q --spider http://127.0.0.1:3000/api/health || exit 1 CMD ["node", "server.js"]