# syntax=docker/dockerfile:1.7
#
# kb-app Dockerfile — multi-stage build producing a slim runtime image.
#
# Stages:
#   1. deps     — install ALL node_modules (the build needs dev deps too)
#   2. builder  — `next build` → emits .next/standalone/ + .next/static/
#   3. runner   — final image: only the standalone tree + runtime extras
#
# The runtime image is ~180 MB on node:20-alpine vs ~1.1 GB with a naive
# single-stage build. The wizard at /setup is reachable on first boot;
# the user provides DATABASE_URL through the browser.

# ── Stage 1: deps ─────────────────────────────────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app

# Native-build toolchain is NOT needed — every dep we use is pure JS
# (bcryptjs is JS-only, mysql2 ships pre-built binaries, jose is JS).
# Skipping python/gcc keeps the deps stage fast.

# Copy lockfile first so the npm-ci layer caches when only source changes.
COPY package.json package-lock.json ./
RUN npm ci --include=dev


# ── Stage 2: builder ──────────────────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app

# Reuse deps from stage 1.
COPY --from=deps /app/node_modules ./node_modules

# Copy the rest of the source. `.dockerignore` excludes node_modules,
# .next, .env files, data/, etc.
COPY . .

# Build-time env: tell Next.js this is a production build. AUTH_SECRET is
# only needed at runtime; placeholder values here keep Auth.js's
# config-loader happy during prerender (it doesn't actually call sign-in
# during build).
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

RUN npm run build

# Verify the standalone output exists — `output: "standalone"` in
# next.config.ts is what produces it. Fails the build loudly if someone
# accidentally removes the config.
RUN test -d /app/.next/standalone || (echo "ERROR: .next/standalone not found — is output: 'standalone' set in next.config.ts?" && exit 1)


# ── Stage 3: runner ───────────────────────────────────────────────────────────
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Default port — overridable via PORT env. Compose maps host 3000 → 3000.
ENV PORT=3000
ENV HOSTNAME=0.0.0.0

# `dumb-init` reaps zombie processes when kb-app is PID 1 (default in
# Docker). Without it, signals from `docker stop` would arrive at node
# instead of bubbling through a proper init.
RUN apk add --no-cache dumb-init

# Non-root user — runs as uid 1001. The /data volume needs the same uid.
RUN addgroup -g 1001 -S kb && \
    adduser  -u 1001 -S kb -G kb

# Standalone output: server.js + minimal node_modules.
COPY --from=builder --chown=kb:kb /app/.next/standalone ./
# Static assets aren't bundled into standalone — copy them in alongside.
COPY --from=builder --chown=kb:kb /app/.next/static ./.next/static
COPY --from=builder --chown=kb:kb /app/public ./public

# Migrations are read at wizard-finalize time via fs.readFile — not
# bundled by Next.js. Copy them into the same path the runtime expects
# (`<cwd>/db/migrations/`).
COPY --from=builder --chown=kb:kb /app/db/migrations ./db/migrations

# Entrypoint: generates AUTH_SECRET on first boot if missing, ensures
# /data is writable, then hands off to node server.js.
COPY --chown=kb:kb docker/entrypoint.sh /usr/local/bin/kb-entrypoint
RUN chmod +x /usr/local/bin/kb-entrypoint

# `/data` is the persistent volume — config.json, .setup-lock, blob
# storage, .env-backups. Operator mounts this in compose.
RUN mkdir -p /data && chown kb:kb /data
ENV KB_DATA_DIR=/data
VOLUME ["/data"]

USER kb
EXPOSE 3000

# dumb-init wraps node so SIGTERM from `docker stop` is forwarded cleanly.
ENTRYPOINT ["dumb-init", "--", "kb-entrypoint"]
CMD ["node", "server.js"]
