| # syntax=docker/dockerfile:1.7 | |
| # βββ Stage 1: deps βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Install only production dependencies. node_modules cached aggressively. | |
| FROM node:20-alpine AS deps | |
| RUN apk add --no-cache libc6-compat | |
| WORKDIR /app | |
| # Lockfile-first copy enables Docker layer caching. Re-runs only on | |
| # package.json / lockfile changes, not on source edits. | |
| COPY package.json package-lock.json* ./ | |
| RUN npm ci --no-audit --no-fund | |
| # βββ Stage 2: builder ββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Compile Next.js with standalone output. Outputs go to .next/standalone | |
| # and .next/static β both copied into the runtime stage. | |
| FROM node:20-alpine AS builder | |
| WORKDIR /app | |
| COPY --from=deps /app/node_modules ./node_modules | |
| COPY . . | |
| # Build-time env: NEXT_TELEMETRY_DISABLED suppresses telemetry; no secrets here. | |
| # HF_TOKEN is intentionally NOT baked in β it's passed at runtime. | |
| ENV NEXT_TELEMETRY_DISABLED=1 | |
| RUN npm run build | |
| # βββ Stage 3: runner βββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Minimal runtime image. Only contains the compiled standalone server, | |
| # static assets, and node binary. | |
| FROM node:20-alpine AS runner | |
| WORKDIR /app | |
| ENV NODE_ENV=production | |
| ENV NEXT_TELEMETRY_DISABLED=1 | |
| # Default to 7860 (HF Spaces convention). Local docker-compose overrides to 3000. | |
| ENV PORT=7860 | |
| ENV HOSTNAME=0.0.0.0 | |
| # Run as non-root for defense-in-depth (HF Spaces injects USER 1000 too). | |
| RUN addgroup --system --gid 1001 nextjs \ | |
| && adduser --system --uid 1001 --ingroup nextjs nextjs | |
| # Standalone bundle: server.js + minimum required node_modules. | |
| COPY --from=builder --chown=nextjs:nextjs /app/.next/standalone ./ | |
| COPY --from=builder --chown=nextjs:nextjs /app/.next/static ./.next/static | |
| COPY --from=builder --chown=nextjs:nextjs /app/public ./public | |
| USER nextjs | |
| EXPOSE 7860 | |
| # Healthcheck β uses ash builtin to expand $PORT. | |
| HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ | |
| CMD wget --quiet --tries=1 --spider "http://localhost:${PORT:-7860}" || exit 1 | |
| # server.js (generated by next build with standalone output) reads PORT env var. | |
| CMD ["node", "server.js"] | |