# Multi-stage Dockerfile for Next.js CMS # Stage 1: Build FROM node:22-alpine AS builder WORKDIR /app # Copy package files COPY package.json pnpm-lock.yaml ./ # Install dependencies (pnpm pinned so builds are reproducible) RUN npm install -g pnpm@10.22.0 && pnpm install --frozen-lockfile # Copy source code COPY . . # Refuse to build when a production dependency has a known critical # vulnerability. Runs after COPY so it isn't skipped by the layer cache on # deploys that only change source code. RUN pnpm audit --prod --audit-level critical ARG DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" ENV DATABASE_URL=$DATABASE_URL # Generate Prisma client RUN pnpm exec prisma generate # Build Next.js application RUN pnpm run build # Stage 2: Production Runtime FROM node:22-alpine WORKDIR /app # Install dumb-init for proper signal handling RUN apk add --no-cache dumb-init # Copy from builder: node_modules and .next build output. # Files stay root-owned so the app user cannot rewrite its own code; only the # Next.js cache directory is writable. COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json RUN mkdir -p .next/cache && chown node:node .next/cache # Set production environment ENV NODE_ENV=production # Run as the unprivileged "node" user (uid 1000) instead of root USER node # Expose port 3000 EXPOSE 3000 # Use dumb-init to handle signals properly ENTRYPOINT ["dumb-init", "--"] # Start Next.js production server (directly, so npm doesn't need a writable home) CMD ["node", "node_modules/next/dist/bin/next", "start"]