Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Das Volume `files` wird auf /data/files gelegt. Diesen Pfad gab es im Image nicht, also legte Docker ihn beim Einhaengen selbst an - als root mit 755. Der Dienst laeuft aber als afis (1001) und bekam beim ersten Schreibversuch EACCES. Betroffen war jeder Upload: Anhaenge, Lichtbilder, und auch die fertigen PDF-Abzuege, die in derselben Ablage landen. Nachgemessen statt vermutet: ein leeres Volume uebernimmt Verzeichnis und Eigentuemer aus dem Image, ein bereits befuelltes behaelt seine alten Rechte. Da bisher nie ein Upload durchkam, ist die Ablage leer - ein Neubau genuegt, das Volume muss nicht weg. Dazu die Ablage in die Statusanzeige. Sie war der zweite stille Ausfall dieser Art nach clamd: ein Zustand, den man nur durch Ausprobieren erfaehrt, gehoert dorthin, wo man ihn sieht. Geprueft wird das Schreibrecht, nicht durch eine Probedatei - ein Healthcheck, der im Sekundentakt schreibt und aufraeumt, waere selbst ein Risiko. Die Pruefung dazu stellt den echten Fall nach, ein leeres gesperrtes Verzeichnis. Mein erster Versuch sperrte nur das Elternverzeichnis, waehrend quarantine/ und store/ darin schon standen und beschreibbar blieben - der Dienst haette weitergearbeitet, und die Anzeige haette zu Recht nichts gemeldet.
110 lines
4.3 KiB
Docker
110 lines
4.3 KiB
Docker
# Image für den Dienst `web`. Standalone-Build, siehe web/next.config.ts.
|
|
#
|
|
# Die Migration läuft im Startkommando des Containers, nicht hier und nicht in
|
|
# einem Init-Container: Dockge zeigt beendete Container nicht dauerhaft an.
|
|
# Begründung in docs/11-betrieb.md.
|
|
#
|
|
# Dafür liegt eine eigenständige Prisma-CLI unter /app/migrator im Image. Sie
|
|
# beim Start aus dem Netz zu ziehen wäre der falsche Weg: der Container startet
|
|
# dann nur, wenn die Registry gerade erreichbar ist.
|
|
|
|
ARG NODE_VERSION=22-alpine
|
|
ARG PNPM_VERSION=9.15.4
|
|
ARG PRISMA_VERSION=7.10.0
|
|
|
|
# --- Abhängigkeiten -----------------------------------------------------------
|
|
FROM node:${NODE_VERSION} AS deps
|
|
ARG PNPM_VERSION
|
|
RUN npm install -g pnpm@${PNPM_VERSION}
|
|
WORKDIR /repo
|
|
|
|
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml .npmrc ./
|
|
COPY packages/db/package.json packages/db/
|
|
COPY web/package.json web/
|
|
COPY worker/package.json worker/
|
|
COPY pdf/package.json pdf/
|
|
|
|
RUN pnpm install --frozen-lockfile --filter @afis/web... --filter @afis/db...
|
|
|
|
# --- Bauen --------------------------------------------------------------------
|
|
FROM node:${NODE_VERSION} AS build
|
|
ARG PRISMA_VERSION
|
|
ARG PNPM_VERSION
|
|
RUN npm install -g pnpm@${PNPM_VERSION}
|
|
WORKDIR /repo
|
|
|
|
COPY --from=deps /repo/node_modules ./node_modules
|
|
COPY --from=deps /repo/packages/db/node_modules ./packages/db/node_modules
|
|
COPY --from=deps /repo/web/node_modules ./web/node_modules
|
|
COPY . .
|
|
|
|
# Prisma-Client erzeugen und @afis/db nach dist übersetzen.
|
|
RUN pnpm --filter @afis/db build
|
|
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
RUN pnpm --filter @afis/web build
|
|
|
|
# Eigenständige Migrations-CLI mit heruntergeladener Schema-Engine.
|
|
# Dazu der Seed: er legt den Grundzustand an - Freigabestufen, Status-Etiketten,
|
|
# Fallarten, Nummernkreise. Ohne ihn startet die Anwendung zwar, aber es laesst
|
|
# sich keine Akte anlegen, weil es keine Vorlage gibt. Er besteht ausschliesslich
|
|
# aus upserts und darf deshalb bei jedem Start mitlaufen.
|
|
RUN mkdir -p /migrator && cd /migrator && \
|
|
npm install --no-package-lock --no-audit --no-fund \
|
|
"prisma@${PRISMA_VERSION}" \
|
|
"@prisma/client@${PRISMA_VERSION}" \
|
|
"@prisma/adapter-pg@${PRISMA_VERSION}" \
|
|
pg tsx && \
|
|
cp -r /repo/packages/db/prisma /migrator/prisma && \
|
|
cp -r /repo/packages/db/src /migrator/src && \
|
|
cp /repo/packages/db/prisma.config.ts /migrator/prisma.config.ts
|
|
|
|
# --- Laufzeit -----------------------------------------------------------------
|
|
FROM node:${NODE_VERSION} AS runtime
|
|
WORKDIR /app
|
|
|
|
ENV NODE_ENV=production \
|
|
NEXT_TELEMETRY_DISABLED=1 \
|
|
PORT=3000 \
|
|
HOSTNAME=0.0.0.0
|
|
|
|
# qpdf säubert hochgeladene PDF-Dateien, siehe docs/07-dateien-und-sicherheit.md.
|
|
# Fehlt es, bleiben PDF-Uploads in der Quarantäne statt ungeprüft durchzugehen.
|
|
RUN apk add --no-cache wget openssl qpdf && \
|
|
addgroup -g 1001 -S afis && \
|
|
adduser -u 1001 -S afis -G afis
|
|
|
|
# Standalone-Ausgabe: server.js plus die tatsächlich benutzten node_modules.
|
|
COPY --from=build --chown=afis:afis /repo/web/.next/standalone/ ./
|
|
COPY --from=build --chown=afis:afis /repo/web/.next/static ./web/.next/static
|
|
COPY --from=build --chown=afis:afis /repo/web/public ./web/public
|
|
|
|
# Migrations-CLI samt Schema, Migrationsordner und Engine.
|
|
COPY --from=build --chown=afis:afis /migrator ./migrator
|
|
|
|
# Die Dateiablage **muss im Image liegen**, sonst kann niemand hochladen.
|
|
#
|
|
# `files` ist ein benanntes Volume und wird auf /data/files gelegt. Existiert
|
|
# dieser Pfad im Image nicht, legt Docker ihn beim Einhaengen selbst an - als
|
|
# root mit 755. Der Dienst laeuft aber als afis (1001) und bekommt beim ersten
|
|
# Schreibversuch EACCES: jeder Upload scheiterte mit einer nichtssagenden
|
|
# Meldung, vom Lichtbild bis zum Anhang.
|
|
#
|
|
# Liegt das Verzeichnis dagegen im Image und ist leer, uebernimmt Docker es
|
|
# samt Eigentuemer in ein leeres Volume. Nachgemessen: ein bereits befuelltes
|
|
# Volume behaelt seine alten Rechte, ein leeres nicht.
|
|
#
|
|
# quarantine/ und store/ gleich mit anlegen - dieselben zwei Verzeichnisse, die
|
|
# lib/files/paths.ts erwartet.
|
|
RUN mkdir -p /data/files/quarantine /data/files/store && \
|
|
chown -R afis:afis /data
|
|
|
|
USER afis
|
|
EXPOSE 3000
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
|
CMD wget -qO- http://localhost:3000/api/health || exit 1
|
|
|
|
# Das Startkommando setzt der Stack (Migration + Server), siehe compose.yaml.
|
|
CMD ["node", "web/server.js"]
|