next build evaluiert beim 'Collecting page data' Server-Routen (u. a. /api/auth/[...nextauth]); src/lib/env.ts validiert beim Import (Fail-Fast) und brach ohne gesetzte Variablen ab. Platzhalter-Env (erfüllt das Zod-Schema) nur für den Build ergänzt — Server-env wird nicht ins Bundle inlined, die builder- Stage landet nicht im Runtime-Image; echte Werte kommen zur Laufzeit aus Compose. Lokal verifiziert: next build läuft mit den Platzhaltern sauber durch (alle Routen). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
103 lines
5.4 KiB
Docker
103 lines
5.4 KiB
Docker
# syntax=docker/dockerfile:1
|
|
# FlorianNetz — multi-stage Build des Next.js-Standalone-Servers.
|
|
# Stufen: deps (Abhängigkeiten) -> builder (Build) -> runner (schlankes Laufzeit-Image).
|
|
# Läuft non-root (UID/GID 1001). Migration + optionaler Seed laufen im Entrypoint
|
|
# vor dem App-Start (siehe docker/entrypoint.sh).
|
|
|
|
ARG NODE_VERSION=22
|
|
|
|
# --- deps: Produktions- und Build-Abhängigkeiten installieren -----------------
|
|
FROM node:${NODE_VERSION}-alpine AS deps
|
|
WORKDIR /app
|
|
# node:alpine bündelt npm 10, das bei plattformfremden optionalen Transitiv-Deps
|
|
# (z. B. @node-rs/argon2 -> *-wasm32-wasi / @emnapi) strenger ist. npm 11 wie im
|
|
# feuerwehr_dashboard verwenden.
|
|
RUN npm install -g npm@11
|
|
# .npmrc erzwingt das ÖFFENTLICHE npm-Registry. Der committete Lockfile wurde
|
|
# gegen einen internen Mirror erzeugt (resolved-URLs zeigen dorthin, daher der
|
|
# npm-ci-Fehler) und wird im Build bewusst NICHT verwendet — Auflösung frisch aus
|
|
# der öffentlichen Registry (gleiches Vorgehen wie feuerwehr_dashboard/frontend).
|
|
COPY package.json .npmrc ./
|
|
RUN npm install --no-audit --no-fund
|
|
|
|
# --- builder: Next.js im Standalone-Modus bauen -------------------------------
|
|
FROM node:${NODE_VERSION}-alpine AS builder
|
|
WORKDIR /app
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
# Build-Zeit-Platzhalter: src/lib/env.ts validiert beim Import (Fail-Fast).
|
|
# `next build` evaluiert beim "Collecting page data" die Server-Routen (u. a.
|
|
# /api/auth/[...nextauth]) -> ohne gesetzte Variablen bricht der Import ab.
|
|
# Diese Werte sind NUR für den Build (erfüllen das Zod-Schema); Server-env wird
|
|
# NICHT ins Bundle inlined und die builder-Stage landet NICHT im Runtime-Image.
|
|
# Echte Werte kommen zur Laufzeit aus docker-compose.
|
|
ENV DATABASE_URL=postgresql://build:build@localhost:5432/build \
|
|
AUTH_SECRET=build_only_placeholder_secret_min_32_chars_long \
|
|
AUTH_URL=https://build.invalid \
|
|
AUTHENTIK_ISSUER=https://build.invalid/application/o/floriannetz/ \
|
|
AUTHENTIK_CLIENT_ID=build \
|
|
AUTHENTIK_CLIENT_SECRET=build
|
|
COPY --from=deps /app/node_modules ./node_modules
|
|
COPY . .
|
|
# next.config.ts setzt output:"standalone" -> erzeugt .next/standalone/server.js.
|
|
RUN npm run build
|
|
# Katalog-Seed zu einer selbstständigen Plain-ESM-Datei (docker/seed.mjs)
|
|
# bündeln, damit RUN_SEED=true im Runner (ohne tsx/src) funktioniert.
|
|
RUN node scripts/build-seed-bundle.mjs
|
|
|
|
# --- runner: minimales Laufzeit-Image ----------------------------------------
|
|
FROM node:${NODE_VERSION}-alpine AS runner
|
|
WORKDIR /app
|
|
ENV NODE_ENV=production
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
ENV PORT=3000
|
|
ENV HOSTNAME=0.0.0.0
|
|
|
|
# Postgres-Client (pg_isready/psql) für die Wait-on-DB-Probe im Entrypoint.
|
|
RUN apk add --no-cache postgresql-client
|
|
|
|
# Non-root-Benutzer (feste UID/GID 1001, wie im Plan gefordert).
|
|
RUN addgroup --system --gid 1001 nodejs \
|
|
&& adduser --system --uid 1001 nextjs
|
|
|
|
# Standalone-Server + statische Assets.
|
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
|
|
|
# Migration zur Laufzeit: Drizzle-Journal + Migrator + pg, plus die Migrationen.
|
|
COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/drizzle-orm ./node_modules/drizzle-orm
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg ./node_modules/pg
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-pool ./node_modules/pg-pool
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-protocol ./node_modules/pg-protocol
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-types ./node_modules/pg-types
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-connection-string ./node_modules/pg-connection-string
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pgpass ./node_modules/pgpass
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-array ./node_modules/postgres-array
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-bytea ./node_modules/postgres-bytea
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-date ./node_modules/postgres-date
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/postgres-interval ./node_modules/postgres-interval
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/split2 ./node_modules/split2
|
|
# Tiefere Transitiv-Abhängigkeiten der pg-Kette mit eagerem require (sonst
|
|
# ERR_MODULE_NOT_FOUND beim Container-Start in docker/migrate.mjs/seed.mjs):
|
|
# pg-types/lib/binaryParsers.js -> require('pg-int8')
|
|
# postgres-interval/index.js -> require('xtend/mutable')
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/pg-int8 ./node_modules/pg-int8
|
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules/xtend ./node_modules/xtend
|
|
|
|
# Migrations-Runner (plain ESM, ohne tsx) + gebündelter Seed + Entrypoint.
|
|
COPY --chown=nextjs:nodejs docker/migrate.mjs ./docker/migrate.mjs
|
|
COPY --from=builder --chown=nextjs:nodejs /app/docker/seed.mjs ./docker/seed.mjs
|
|
COPY --chown=nextjs:nodejs docker/entrypoint.sh ./docker/entrypoint.sh
|
|
RUN chmod +x ./docker/entrypoint.sh
|
|
|
|
USER nextjs
|
|
EXPOSE 3000
|
|
|
|
# Liveness-Probe (öffentlich, ohne Fachdaten).
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=5 \
|
|
CMD wget -q -O - http://127.0.0.1:3000/api/health || exit 1
|
|
|
|
ENTRYPOINT ["./docker/entrypoint.sh"]
|
|
CMD ["node", "server.js"]
|