# syntax=docker/dockerfile:1.7
# Multi-stage build for moneyapp.
#
# Stages:
#   deps    — install all deps with the npm cache mounted, so re-builds
#             reuse it instead of re-downloading every package.
#   builder — compile migrate.ts → migrate.js (drops the runtime tsx
#             dep), build the standalone Next.js bundle, then prune dev
#             dependencies so only runtime packages survive into runner.
#   runner  — minimal final image: standalone Next.js bundle + the
#             pruned production node_modules (better-sqlite3's native
#             binding lives there).
#
# Notes:
#   * We do NOT run `npm run db:generate` at build time. The drizzle/
#     SQL migrations are committed to the repo, so generating them on
#     every build is wasted work and a frequent source of permission /
#     filesystem errors during `docker build`.
#   * `output: "standalone"` in next.config.ts means we don't need to
#     copy the entire builder node_modules into runner — just the bits
#     standalone marks as external (better-sqlite3, drizzle).

FROM node:22-bookworm-slim AS deps
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 build-essential \
  && rm -rf /var/lib/apt/lists/*
COPY app/package.json app/package-lock.json* ./
RUN --mount=type=cache,target=/root/.npm npm ci

FROM node:22-bookworm-slim AS builder
WORKDIR /build
COPY --from=deps /build/node_modules ./node_modules
COPY app/ ./
ENV NEXT_TELEMETRY_DISABLED=1
# Compile the TS migrator into a single self-contained JS file so the
# runtime image doesn't need tsx (50+ MB) just to run migrations.
# better-sqlite3 + drizzle stay external so they resolve from
# node_modules at runtime.
RUN npx --yes esbuild src/db/migrate.ts \
        --bundle --platform=node --target=node22 \
        --external:better-sqlite3 --external:drizzle-orm \
        --outfile=migrate.js
# Next.js's "Collecting page data" phase imports server modules to
# gather route metadata. Auth.js + the Drizzle adapter open a sqlite
# connection on import, so /data has to exist and AUTH_SECRET has to
# be non-empty *during the build* even though both get overridden at
# runtime by the real values.
RUN mkdir -p /tmp/build-db \
    && DATABASE_FILE=/tmp/build-db/moneyapp.db \
       AUTH_SECRET=build-time-placeholder-not-used-at-runtime \
       AUTH_URL=http://localhost:3000 \
       npm run build
# Drop devDependencies so the node_modules we copy into runner has
# only what production needs (no typescript, drizzle-kit, eslint...).
RUN npm prune --omit=dev

FROM node:22-bookworm-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_FILE=/data/moneyapp.db
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
  && rm -rf /var/lib/apt/lists/* \
  && groupadd -r app && useradd -r -g app app \
  && mkdir -p /data && chown app:app /data

COPY --from=builder /build/public ./public
COPY --from=builder /build/.next/standalone ./
COPY --from=builder /build/.next/static ./.next/static
COPY --from=builder /build/drizzle ./drizzle
COPY --from=builder /build/migrate.js ./migrate.js
# Last so it overlays standalone's traced node_modules with the full
# pruned tree — that way better-sqlite3's native .node binding (which
# standalone marks external) is present at runtime.
COPY --from=builder /build/node_modules ./node_modules

USER app
EXPOSE 3000
VOLUME ["/data"]
CMD ["sh", "-c", "node migrate.js && node server.js"]
