perf: docker + next.js build optimisation - dockerignore exclusions, BuildKit cache mounts, backend image reuse, memory limits, next.js bundle tree-shaking

This commit is contained in:
2026-09-20 12:03:58 +05:30
parent fee57e154b
commit 896073ea2d
6 changed files with 317 additions and 170 deletions

View File

@@ -1,54 +1,57 @@
FROM node:20-alpine AS base
# ═══════════════════════════════════════════════════════════════════════════
# Stage 1 – deps: install node_modules with full cache mount
# (This layer is only re-run when package.json / package-lock.json changes)
# ═══════════════════════════════════════════════════════════════════════════
FROM node:20-alpine AS deps
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
# --mount=type=cache keeps the npm cache on the host between builds
# --prefer-offline avoids network fetches when the package is already cached
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
# ═══════════════════════════════════════════════════════════════════════════
# Stage 2 – builder: compile Next.js with build-cache mount for turbo-speed
# ═══════════════════════════════════════════════════════════════════════════
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Set environment variables for build
ENV NEXT_TELEMETRY_DISABLED=1
# Accept API URL as build arg — defaults to Docker-internal service name
ENV NODE_ENV=production
ARG NEXT_PUBLIC_API_URL=http://backend:8000
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
# BACKEND_INTERNAL_URL drives the server-side /api rewrite; must be a build-time
# arg so it is compiled into next.config.ts (which is evaluated at build time).
ARG BACKEND_INTERNAL_URL
ENV BACKEND_INTERNAL_URL=$BACKEND_INTERNAL_URL
RUN npm run build
# Mount the Next.js build cache so incremental compilation is reused
RUN --mount=type=cache,target=/app/.next/cache \
npm run build
# Production image, copy all the files and run next
FROM base AS runner
# ═══════════════════════════════════════════════════════════════════════════
# Stage 3 – runner: minimal production image (no node_modules, no sources)
# ═══════════════════════════════════════════════════════════════════════════
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
# Copy only the standalone server output (no full node_modules)
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
RUN mkdir .next && chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs