# ── Stage 1: Frontend build ─────────────────────────────────────────────────── FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm ci COPY frontend/ ./ # Erst prüfen, dann bauen: Die Tests decken die Stellen ab, die sich ohne # Browser prüfen lassen – Reihenfolgen, Zoomstufen, Suche im Text. Sie laufen # in einer halben Sekunde; ein Abbild mit einer kaputten Sortierung kostet mehr. RUN npm test && npm run build # ── Stage 2: Backend build ──────────────────────────────────────────────────── FROM golang:1.25-alpine AS backend-builder WORKDIR /app RUN apk add --no-cache git COPY backend/go.mod backend/go.sum ./ RUN go mod download COPY backend/ ./ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o aether ./cmd/aether # ── Stage 3: Final image ────────────────────────────────────────────────────── FROM alpine:3.20 # ocrmypdf, tesseract und poppler machen den Inhalt von PDFs und Scans # durchsuchbar (internal/documents). Sie wiegen zusammen rund 330 MB – der # Löwenanteil des Images. Wer das nicht braucht, kann die zweite apk-Zeile # streichen und OCR_ENABLED=false setzen; der Server prüft beim Start selbst, # ob die Programme da sind, und läuft sonst ohne Volltextsuche weiter. # # tesseract-ocr-data-deu/-eng sind die Sprachmodelle. Ohne sie startet # tesseract zwar, erkennt aber nichts – das Paket tesseract-ocr allein bringt # keine mit. RUN apk add --no-cache ca-certificates tzdata && \ apk add --no-cache ocrmypdf tesseract-ocr tesseract-ocr-data-deu \ tesseract-ocr-data-eng poppler-utils && \ addgroup -S aether && adduser -S aether -G aether && \ mkdir -p /data/users /data/thumbs /data/tmp && \ chown -R aether:aether /data WORKDIR /app # Wo die Blobs liegen. Steht **hier**, nicht nur in der .env: Die Voreinstellung # im Code ist "./data" – richtig für ein Binary, das man von Hand startet, # falsch im Container, wo daraus /app/data würde. Genau das ist beim ersten # Aufbau aus der schlanken .env passiert: Migrationen liefen durch, dann # "mkdir /app/data: permission denied". Das Abbild kennt seine eigene # Verzeichnisstruktur besser als jede Beispieldatei; eine .env darf es # weiterhin überschreiben. ENV STORAGE_PATH=/data \ TMP_PATH=/data/tmp # Tesseract vermisst beim allerersten Aufruf die OpenCL-Geräte und schreibt # dabei einen langen Diagnoseblock nach stderr. ocrmypdf ruft davor # `tesseract --list-langs` auf und liest die Antwort – der Block bringt seinen # Parser aus dem Tritt, und der Aufruf endet mit „Tesseract failed to report # available languages" (Exit 3). # # Das Ergebnis der Vermessung landet als .dat im Arbeitsverzeichnis; ab dann ist # die Ausgabe kurz genug. Ohne diese Zeile scheitert deshalb genau das erste # Dokument nach jedem Neustart eines frischen Containers – gemessen, nicht # vermutet: Versuch 1 Exit 3, Versuch 2 erfolgreich. Der Wiederholungszähler # fängt es zwar auf, aber ein Fehlschlag, den man einbauen kann, gehört nicht # in den Log. RUN tesseract --list-langs >/dev/null 2>&1 || true COPY --from=backend-builder /app/aether ./aether COPY --from=frontend-builder /app/frontend/dist ./static RUN chown -R aether:aether /app USER aether EXPOSE 8080 ENTRYPOINT ["./aether"]