Legt den kompletten Unterbau an, auf dem die Fachmodule aufsetzen. Betrieb: - docker-compose.yml mit Traefik, App, Worker, PostgreSQL 17 + PostGIS, Redis, MinIO und Gotenberg; Entwicklungs-Override ohne Traefik, dafuer mit Mailpit und Adminer - Mehrstufiges Dockerfile: eigener Benutzer, tini als PID 1, Healthcheck - .env.example mit einem Kommentar zu jeder Variable Mandantentrennung: - Row Level Security auf allen mandantenpflichtigen Tabellen - Zwei getrennte Datenbankverbindungen: florian_app mit aktiver RLS fuer die Anwendung, Eigentuemer-Zugang nur fuer Migration, Seed und Anmeldung - fuerOrganisation() als einziger Weg zu Fachdaten Nachvollziehbarkeit: - AuditLog append-only per Trigger; UPDATE ist dauerhaft gesperrt, DELETE nur im gekennzeichneten Aufbewahrungslauf - Geheimnisse werden vor dem Schreiben entfernt Anmeldung: - OIDC gegen ein extern betriebenes authentik, Rollen aus Gruppen nur als Vorbelegung beim ersten Login - Lokaler E-Mail/Passwort-Login als Rueckfallweg - Terminal per PIN und RFID mit zwei Schranken (Geraet, dann Person) Weiteres: - lib/authz.ts mit 9 Rollen und 38 Einzelrechten - lib/storage.ts (MinIO), lib/audit.ts, lib/warteschlange.ts (BullMQ) - Integrationen fuer Mail, Telegram und LLM hinter abschaltbaren Interfaces - tRPC v11, Basis-Layout mit Seitenleiste und dunkler Darstellung - Seed mit einer Beispielwehr: 16 Personen, 3 Fahrzeuge, 40 Geraete - 32 Vitest-Tests (davon 8 gegen eine echte Datenbank), 10 Playwright-Tests - README, Anleitung mit Screenshots, Roadmap, Aufwandsschaetzung, Devlog, Versionierungsregeln und Changelog Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
112 lines
4.3 KiB
Docker
112 lines
4.3 KiB
Docker
# =============================================================================
|
|
# 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"]
|