# ============================================================================= # FLORIAN — Mehrstufiger Build. # deps : Abhängigkeiten installieren (wird gecacht) # builder : Prisma-Client erzeugen, Next.js bauen, Worker transpilieren # runner : schlankes Laufzeit-Image, non-root, mit Healthcheck # ============================================================================= ARG NODE_VERSION=22.20.0 # ----------------------------------------------------------------------------- # Stufe 1: Abhängigkeiten # ----------------------------------------------------------------------------- FROM node:${NODE_VERSION}-bookworm-slim AS deps WORKDIR /app # openssl wird von Prisma benötigt, ca-certificates für ausgehendes TLS. RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates \ && rm -rf /var/lib/apt/lists/* ENV NEXT_TELEMETRY_DISABLED=1 COPY package.json package-lock.json* .npmrc ./ RUN npm ci --include=dev # ----------------------------------------------------------------------------- # Stufe 2: Build # ----------------------------------------------------------------------------- FROM node:${NODE_VERSION}-bookworm-slim AS builder WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates \ && rm -rf /var/lib/apt/lists/* ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production COPY --from=deps /app/node_modules ./node_modules COPY . . # Prisma-Client aus dem Schema erzeugen — ohne Datenbankverbindung. RUN npx prisma generate # Der Next.js-Build braucht formal gültige Variablen, verbindet sich aber nicht. # Die echten Werte kommen zur Laufzeit aus der Umgebung. RUN DATABASE_URL="postgresql://build:build@localhost:5432/build?schema=public" \ DIRECT_DATABASE_URL="postgresql://build:build@localhost:5432/build?schema=public" \ AUTH_SECRET="build_time_platzhalter_mindestens_32_zeichen" \ KIOSK_DEVICE_SECRET="build_time_platzhalter" \ SKIP_ENV_VALIDATION=1 \ npm run build # Worker separat nach CommonJS transpilieren, damit im Runtime-Image kein # TypeScript-Toolchain mehr nötig ist. RUN npx esbuild src/worker/index.ts \ --bundle \ --platform=node \ --target=node22 \ --format=cjs \ --packages=external \ --sourcemap \ --outfile=dist/worker/index.js # ----------------------------------------------------------------------------- # Stufe 3: Laufzeit # ----------------------------------------------------------------------------- FROM node:${NODE_VERSION}-bookworm-slim AS runner WORKDIR /app RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates tini \ && rm -rf /var/lib/apt/lists/* ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 ENV TZ=Europe/Berlin # Eigener, unprivilegierter Benutzer. Läuft NICHT als root. RUN groupadd --system --gid 1001 florian \ && useradd --system --uid 1001 --gid florian --home /app --shell /usr/sbin/nologin florian # Standalone-Ausgabe von Next.js: enthält nur die tatsächlich benötigten Module. COPY --from=builder --chown=florian:florian /app/.next/standalone ./ COPY --from=builder --chown=florian:florian /app/.next/static ./.next/static COPY --from=builder --chown=florian:florian /app/public ./public # Worker-Bundle und dessen Laufzeitabhängigkeiten. COPY --from=builder --chown=florian:florian /app/dist ./dist # Prisma: Schema und Migrationen fürs Deployment, plus erzeugter Client. COPY --from=builder --chown=florian:florian /app/prisma ./prisma COPY --from=builder --chown=florian:florian /app/node_modules/.prisma ./node_modules/.prisma COPY --from=builder --chown=florian:florian /app/node_modules/@prisma ./node_modules/@prisma COPY --from=builder --chown=florian:florian /app/node_modules/prisma ./node_modules/prisma COPY --chown=florian:florian docker/entrypoint.sh /usr/local/bin/entrypoint.sh COPY --chown=florian:florian docker/healthcheck.js /app/healthcheck.js RUN chmod +x /usr/local/bin/entrypoint.sh USER florian EXPOSE 3000 # Prüft den Endpunkt /api/health — der fragt Datenbank und Redis mit ab. HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \ CMD node /app/healthcheck.js || exit 1 # tini als PID 1, damit Signale sauber durchgereicht werden. ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"] CMD ["node", "server.js"]