diff --git a/Dockerfile b/Dockerfile index 4db8d4b..bf4fce4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,66 +1,112 @@ -# syntax=docker.io/docker/dockerfile:1 +# ============================================ +# Stage 1: Dependencies Installation Stage +# ============================================ -FROM node:18-alpine AS base +# IMPORTANT: Node.js Version Maintenance +# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing. +# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version. +ARG NODE_VERSION=24.13.0-slim -# 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 +FROM node:${NODE_VERSION} AS dependencies + +# Set working directory WORKDIR /app -# Install dependencies based on the preferred package manager +# Copy package-related files first to leverage Docker's caching mechanism COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ -RUN \ - if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ - elif [ -f package-lock.json ]; then npm ci; \ - elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ - else echo "Lockfile not found." && exit 1; \ + +# Install project dependencies with frozen lockfile for reproducible builds +RUN --mount=type=cache,target=/root/.npm \ + --mount=type=cache,target=/usr/local/share/.cache/yarn \ + --mount=type=cache,target=/root/.local/share/pnpm/store \ + if [ -f package-lock.json ]; then \ + npm ci --no-audit --no-fund; \ + elif [ -f yarn.lock ]; then \ + corepack enable yarn && yarn install --frozen-lockfile --production=false; \ + elif [ -f pnpm-lock.yaml ]; then \ + corepack enable pnpm && pnpm install --frozen-lockfile; \ + else \ + echo "No lockfile found." && exit 1; \ fi +# ============================================ +# Stage 2: Build Next.js application in standalone mode +# ============================================ -# Rebuild the source code only when needed -FROM base AS builder +FROM node:${NODE_VERSION} AS builder + +# Set working directory WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules + +# Copy project dependencies from dependencies stage +COPY --from=dependencies /app/node_modules ./node_modules + +# Copy application source code COPY . . +ENV NODE_ENV=production + # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry # Uncomment the following line in case you want to disable telemetry during the build. # ENV NEXT_TELEMETRY_DISABLED=1 -RUN \ - if [ -f yarn.lock ]; then yarn run build; \ - elif [ -f package-lock.json ]; then npm run build; \ - elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ - else echo "Lockfile not found." && exit 1; \ +# Build Next.js application +# If you want to speed up Docker rebuilds, you can cache the build artifacts +# by adding: --mount=type=cache,target=/app/.next/cache +# This caches the .next/cache directory across builds, but it also prevents +# .next/cache/fetch-cache from being included in the final image, meaning +# cached fetch responses from the build won't be available at runtime. +RUN if [ -f package-lock.json ]; then \ + npm run build; \ + elif [ -f yarn.lock ]; then \ + corepack enable yarn && yarn build; \ + elif [ -f pnpm-lock.yaml ]; then \ + corepack enable pnpm && pnpm build; \ + else \ + echo "No lockfile found." && exit 1; \ fi -# Production image, copy all the files and run next -FROM base AS runner +# ============================================ +# Stage 3: Run Next.js application +# ============================================ + +FROM node:${NODE_VERSION} AS runner + +# Set working directory WORKDIR /app +# Set production environment variables ENV NODE_ENV=production -# Uncomment the following line in case you want to disable telemetry during runtime. +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the run time. # ENV NEXT_TELEMETRY_DISABLED=1 -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs +# Copy production assets +COPY --from=builder --chown=node:node /app/public ./public -COPY --from=builder /app/public ./public +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown node:node .next # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=node:node /app/.next/standalone ./ +COPY --from=builder --chown=node:node /app/.next/static ./.next/static -USER nextjs +# If you want to persist the fetch cache generated during the build so that +# cached responses are available immediately on startup, uncomment this line: +# COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache +# Switch to non-root user for security best practices +USER node + +# Expose port 3000 to allow HTTP traffic EXPOSE 3000 -ENV PORT=3000 - -# server.js is created by next build from the standalone output -# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output -ENV HOSTNAME="0.0.0.0" +# Start Next.js standalone server CMD ["node", "server.js"] \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c133409..b575f7d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -11,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -19,9 +23,19 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }