# syntax=docker/dockerfile:1 # Multi-stage Dockerfile for Next.js 16 (App Router, output: 'standalone'). # Based on the official Next.js Docker example, tuned for Coolify. # References: # https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile # https://coolify.io/docs/applications/nextjs # ---------------------------------------------------------------------------- # Base image # ---------------------------------------------------------------------------- ARG NODE_VERSION=22 FROM node:${NODE_VERSION}-alpine AS base # libc6-compat is recommended by the Next.js image for some native deps (sharp, etc.) RUN apk add --no-cache libc6-compat WORKDIR /app # ---------------------------------------------------------------------------- # 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, # required because react-google-map-picker declares a React 17 peer). # --include=dev forces devDependencies even when the platform injects # NODE_ENV=production (Coolify does this on all stages); the build needs # devDeps such as babel-plugin-react-compiler (React Compiler) and typescript. RUN npm ci --include=dev --no-audit --no-fund # ---------------------------------------------------------------------------- # 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 toggle Google Analytics + robots noindex, so it must also be present # at build time. # ---------------------------------------------------------------------------- FROM base AS builder WORKDIR /app # Build-time arguments (Coolify: set these as Build Variables) ARG NEXT_PUBLIC_API_URL ARG DEV_MODE=FALSE # Expose them to `next build` ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL ENV DEV_MODE=$DEV_MODE ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production COPY --from=deps /app/node_modules ./node_modules COPY . . # Next 16 builds with Turbopack by default; output: 'standalone' emits # .next/standalone/server.js plus a trimmed node_modules. RUN npm run build # ---------------------------------------------------------------------------- # 3. Runner — minimal production image running the standalone server. # ---------------------------------------------------------------------------- FROM base AS runner WORKDIR /app 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 # Run as a non-root user for security. RUN addgroup --system --gid 1001 nodejs \ && adduser --system --uid 1001 nextjs # 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 USER nextjs EXPOSE 3000 # Container-level health probe (also referenced by docker-compose / Coolify). HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD node -e "require('http').get('http://127.0.0.1:3000/',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))" # server.js is generated by Next.js at the root of the standalone output. CMD ["node", "server.js"]