MEGAINFRA-WEBAPP/Dockerfile

97 lines
3.8 KiB
Docker

# 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"]