diff --git a/.dockerignore b/.dockerignore index 41827fe..ef02e3f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,10 +14,10 @@ build coverage .nyc_output -# Environment files -.env*.local -.env.development -.env.test +# Environment files — never bake secrets into the image; pass via build args / runtime env +.env +.env.* +!.env.example # IDE .vscode diff --git a/dockerfile b/dockerfile index 2f9804a..e95e09c 100644 --- a/dockerfile +++ b/dockerfile @@ -1,48 +1,80 @@ -# Dockerfile for Next.js 14 - Optimized for Production +# syntax=docker/dockerfile:1 +# Multi-stage Dockerfile for Next.js 15 (App Router, output: 'standalone') +# Based on the official Next.js Docker example, tuned for Coolify. +# Reference: https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile -# Use the official Node.js image with Alpine Linux for a smaller footprint +# ---------------------------------------------------------------------------- +# Base image +# ---------------------------------------------------------------------------- FROM node:22-alpine AS base - -# Set the working directory in the container +# libc6-compat is recommended by the Next.js image for some native deps (sharp, etc.) +RUN apk add --no-cache libc6-compat WORKDIR /app -# Copy package.json and package-lock.json (or yarn.lock) -COPY package*.json ./ +# ---------------------------------------------------------------------------- +# 1. Dependencies — installed in a dedicated layer so they are cached as long +# as package.json / lockfile / .npmrc do not change. +# ---------------------------------------------------------------------------- +FROM base AS deps +COPY package.json package-lock.json .npmrc ./ +# Reproducible install honouring the lockfile (.npmrc provides legacy-peer-deps). +RUN npm ci -# Install dependencies - leveraging Docker cache -RUN npm i --force +# ---------------------------------------------------------------------------- +# 2. Builder — produces the standalone output. +# NEXT_PUBLIC_* values are inlined into the client bundle at BUILD time, +# so they must be passed as build args. DEV_MODE is read in app/layout.js +# to enable Google Analytics, so it must also be present at build time. +# ---------------------------------------------------------------------------- +FROM base AS builder +WORKDIR /app -# Copy the rest of the application code +# Build-time arguments (Coolify: set these as Build Variables) +ARG NEXT_PUBLIC_API_URL +ARG NEXT_PUBLIC_CLIENT_ID +ARG NEXT_PUBLIC_CLIENT_SECRET +ARG DEV_MODE=FALSE + +# Expose them to `next build` +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL +ENV NEXT_PUBLIC_CLIENT_ID=$NEXT_PUBLIC_CLIENT_ID +ENV NEXT_PUBLIC_CLIENT_SECRET=$NEXT_PUBLIC_CLIENT_SECRET +ENV DEV_MODE=$DEV_MODE +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production + +COPY --from=deps /app/node_modules ./node_modules COPY . . -# Build the Next.js application RUN npm run build -# Stage 2: Production image - smaller and leaner -FROM node:22-alpine AS runner - -# Set the working directory +# ---------------------------------------------------------------------------- +# 3. Runner — minimal production image running the standalone server. +# ---------------------------------------------------------------------------- +FROM base AS runner WORKDIR /app -# Set environment variables -ENV NODE_ENV $NODE_ENV -ENV NEXT_TELEMETRY_DISABLED 1 +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +# The standalone server must bind to all interfaces inside the container. +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 -# Add a non-root user for security -RUN addgroup -g 1001 -S nodejs -RUN adduser -S nextjs -u 1001 +# Run as a non-root user for security. +RUN addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs -# Copy only the necessary files from the builder stage -COPY --from=base /app/next.config.js ./ -COPY --from=base /app/public ./public -COPY --from=base --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=base --chown=nextjs:nodejs /app/.next/static ./.next/static +# Public assets (served by the standalone server). +COPY --from=builder /app/public ./public + +# Standalone output already contains a minimal node_modules + server.js. +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +# Static assets are NOT included in standalone and must be copied separately. +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static -# Change ownership of all copied files to the non-root user USER nextjs -# Expose the port Next.js listens on EXPOSE 3000 -# Command to start the Next.js server in production mode -CMD ["node", "server.js"] \ No newline at end of file +# server.js is generated by Next.js at the root of the standalone output. +CMD ["node", "server.js"]