docs: README, Datenschutztexte, Dockerfile und Sicherungsskripte

Damit ist Etappe 1 abgeschlossen und betriebsfertig dokumentiert.

README mit beiden Betriebswegen. Docker Compose auf eigenem Server ist
der Hauptweg; Vercel und Neon sind vollstaendig beschrieben, aber mit
einer klaren Begruendung, warum ich davon abrate: beide Anbieter sind
US-Unternehmen und unterliegen auch bei EU-Region potenziell dem
CLOUD-Act. Bei Gesundheitsdaten einer nicht entscheidungsfaehigen Person
ist das eine Abwaegung, die man bewusst treffen muss.

Reverse-Proxy-Beispiele fuer Caddy, nginx und Traefik - der Proxy selbst
ist nicht Teil des Compose-Setups, weil er auf dem Zielserver schon
steht. Die Datenbank hat keine Portfreigabe.

Sicherung: pg_dump schreibt direkt in die Verschluesselung, der Klartext
landet nie auf der Platte. Danach eine Selbstpruefung - laesst sich die
Datei nicht wieder entschluesseln, endet der Lauf mit Fehler statt mit
einem stillen Scheinerfolg. Der jeweils erste Stand eines Monats bleibt
dauerhaft stehen.

Die README sagt ausdruecklich, was das Backup NICHT enthaelt: den
Schluessel ENC_KEY_V1 und die Dateien im Objektspeicher. Dazu eine
Anleitung, die Wiederherstellung einmal im Jahr zu ueben - mit dem
letzten Schritt, in der Anwendung nachzusehen, ob die Diagnosen im
Klartext erscheinen. Erst dann stimmt nicht nur die Datenbank, sondern
auch der Schluessel.

Verarbeitungsverzeichnis und Datenschutzhinweise als ausfuellbare
Textbausteine. Die Hinweise sind in einfacher Sprache geschrieben - sie
sollen der betreuten Person vorgelesen werden koennen. Die Seite
/datenschutz liest denselben Text aus docs/, damit es nicht zwei
Fassungen gibt, die auseinanderlaufen.

Dockerfile in drei Stufen mit Tesseract, deutschen Sprachdaten und
poppler-utils. Zeitzone Europe/Berlin ist gesetzt - ohne sie rechnet der
Server in UTC und verschiebt Fristen um einen Tag.

Etappe: 1
Phase: Fundament
This commit is contained in:
Florian Wilde
2026-08-22 02:35:08 +02:00
parent 6b4df4c9cb
commit 3833a9dd8f
141 changed files with 30302 additions and 66 deletions
+93
View File
@@ -0,0 +1,93 @@
# =============================================================================
# Betreuungs-Cockpit - Produktionsabbild
# =============================================================================
#
# Drei Stufen, damit im laufenden Abbild weder Quelltext noch Bauwerkzeuge
# liegen. Das Ergebnis ist rund 400 MB - der groesste Anteil davon sind
# Tesseract und die deutschen Sprachdaten fuer die Texterkennung.
#
# Basis ist Alpine: kleiner Angriffsflaeche, und @node-rs/argon2 sowie
# Prisma bringen fertige musl-Binaerdateien mit.
# =============================================================================
# --- Stufe 1: Abhaengigkeiten -----------------------------------------------
FROM node:22-alpine AS abhaengigkeiten
WORKDIR /app
# Prisma und OpenSSL gehoeren zusammen.
RUN apk add --no-cache libc6-compat openssl
COPY package.json package-lock.json ./
COPY prisma ./prisma
# "npm ci" mit dem Lockfile - reproduzierbar, keine Ueberraschungen beim
# naechsten Bau. Der postinstall-Schritt erzeugt den Prisma-Client.
RUN npm ci --ignore-scripts \
&& npx prisma generate
# --- Stufe 2: Bauen ----------------------------------------------------------
FROM node:22-alpine AS bau
WORKDIR /app
RUN apk add --no-cache libc6-compat openssl
COPY --from=abhaengigkeiten /app/node_modules ./node_modules
COPY . .
# Beim Bauen werden keine echten Geheimnisse gebraucht: src/lib/env.ts
# prueft erst beim Zugriff. Diese Platzhalter erfuellen nur die Typen.
ENV NEXT_TELEMETRY_DISABLED=1
ENV SKIP_ENV_VALIDATION=1
RUN npm run build
# --- Stufe 3: Laufzeit -------------------------------------------------------
FROM node:22-alpine AS laufzeit
WORKDIR /app
# tesseract-ocr + deutsche Sprachdaten fuer die Texterkennung,
# poppler-utils fuer pdftoppm (PDF-Seiten zu Bildern),
# tzdata fuer Europe/Berlin - sonst rechnet der Server in UTC und
# verschiebt Fristen um einen Tag.
RUN apk add --no-cache \
libc6-compat \
openssl \
tzdata \
tesseract-ocr \
tesseract-ocr-data-deu \
poppler-utils \
&& cp /usr/share/zoneinfo/Europe/Berlin /etc/localtime \
&& echo "Europe/Berlin" > /etc/timezone
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV TZ=Europe/Berlin
ENV PORT=3000
ENV HOSTNAME=0.0.0.0
# Nicht als root laufen.
RUN addgroup --system --gid 1001 cockpit \
&& adduser --system --uid 1001 --ingroup cockpit cockpit
# Der Standalone-Build von Next.js bringt nur mit, was wirklich gebraucht wird.
COPY --from=bau --chown=cockpit:cockpit /app/.next/standalone ./
COPY --from=bau --chown=cockpit:cockpit /app/.next/static ./.next/static
COPY --from=bau --chown=cockpit:cockpit /app/public ./public
# Prisma-Schema, Migrationen und SQL-Skripte fuer "prisma migrate deploy"
# im laufenden Behaelter.
COPY --from=bau --chown=cockpit:cockpit /app/prisma ./prisma
COPY --from=bau --chown=cockpit:cockpit /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=bau --chown=cockpit:cockpit /app/node_modules/@prisma ./node_modules/@prisma
COPY --from=bau --chown=cockpit:cockpit /app/node_modules/prisma ./node_modules/prisma
# Ablage fuer den Dateitreiber, falls kein Objektspeicher benutzt wird.
RUN mkdir -p /app/daten/dokumente && chown -R cockpit:cockpit /app/daten
USER cockpit
EXPOSE 3000
# Migrationen laufen beim Start, danach die Anwendung. Bewusst hier und
# nicht in einem eigenen Behaelter: so kann die Datenbank nie hinter dem
# Programmstand zurueckbleiben.
CMD ["sh", "-c", "node node_modules/prisma/build/index.js migrate deploy && node server.js"]