# syntax=docker/dockerfile:1.7 # Multi-stage build for the Next.js 16 app. # # Build context: apps/web/ (set "Base Directory" in Coolify) # Required env at runtime: PORT (default 4004), HOSTNAME=0.0.0.0, # POCKETBASE_URL, GMAIL_SMTP_USER, GMAIL_SMTP_APP_PASSWORD # ─── Stage 1: install deps ──────────────────────────────────────────────────── FROM node:22-bookworm-slim AS deps WORKDIR /app # Install pnpm globally via npm (avoids corepack's per-user cache dance # which fails for the non-root runtime user). RUN npm install -g pnpm@10.30.3 # Copy only the manifests so the install layer caches well COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN pnpm install --frozen-lockfile # ─── Stage 2: build ──────────────────────────────────────────────────────────── FROM node:22-bookworm-slim AS builder WORKDIR /app RUN npm install -g pnpm@10.30.3 COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 RUN pnpm build # ─── Stage 3: runtime ────────────────────────────────────────────────────────── FROM node:22-bookworm-slim AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 # Next.js defaults to binding localhost, which doesn't work in a container. # Coolify's Traefik reverse-proxies to this port. ENV HOSTNAME=0.0.0.0 ENV PORT=4004 RUN npm install -g pnpm@10.30.3 # Non-root user with a proper home dir (the `-m` is critical — without it # `useradd --system` skips home-dir creation, and pnpm/corepack can't write # their cache to /home/nextjs). RUN groupadd --system --gid 1001 nodejs \ && useradd -m --system --uid 1001 --gid nodejs --home /home/nextjs nextjs # Copy the build artifacts COPY --from=builder --chown=nextjs:nodejs /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json USER nextjs EXPOSE 4004 # Health check: hit the login page (no auth required) using node's built-in http. # If you switch the Coolify UI to a curl-based healthcheck, this still runs # as a fallback inside the container. HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD node -e "require('http').get('http://127.0.0.1:'+process.env.PORT+'/login',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))" CMD ["pnpm", "start"]