# node:alpine = latest Node within the supported LTS major (tracks the newest # patch automatically; currently v26.x, which satisfies package.json's # engines ">=26.8.1 <27"). FROM node:alpine AS builder WORKDIR /app ENV NEXT_TELEMETRY_DISABLED=1 # Real release id supplied by CI (ci-deploy.sh) so next.config.ts skips git # entirely (there is no .git in the build context). Defaults to "unknown". ARG NEXT_DEPLOYMENT_ID="unknown" ENV NEXT_DEPLOYMENT_ID="$NEXT_DEPLOYMENT_ID" # pnpm install is always pinned to the version in package.json's # `packageManager` field (pnpm auto-selects it on install), so this global # install only needs to exist as a bootstrap and follows the active major. RUN apk add --no-cache git \ && npm install -g pnpm@latest # pnpm-workspace.yaml + .npmrc must be present too: the lockfile records the # overrides from pnpm-workspace.yaml, and --frozen-lockfile rejects a build # where the workspace config is absent (ERR_PNPM_LOCKFILE_CONFIG_MISMATCH). COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* .npmrc* ./ # pnpm fetch: download all deps into $PNPM_STORE first, so only the lockfile # change (not source changes) invalidates the network-heavy download layer. RUN pnpm fetch --ignore-scripts RUN pnpm install --frozen-lockfile --ignore-scripts --offline COPY . . RUN pnpm run build FROM node:alpine AS runner WORKDIR /app ENV NODE_ENV=production \ NEXT_TELEMETRY_DISABLED=1 \ PORT=3002 \ HOSTNAME=0.0.0.0 RUN apk add --no-cache tini \ && addgroup -g 33 -S nextjs && adduser -u 33 -S -G nextjs nextjs COPY --from=builder --chown=nextjs:nextjs /app/public ./public COPY --from=builder --chown=nextjs:nextjs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nextjs /app/.next/static ./.next/static USER nextjs EXPOSE 3002 ENTRYPOINT ["/sbin/tini", "--"] CMD ["node", "server.js"]