# syntax=docker/dockerfile:1 # SwyxWeb als ein Container: Das React-Frontend wird gebaut, in die statischen # Ressourcen des Backends gelegt und zusammen mit ihm in ein Jar gepackt. # Ausgeliefert wird am Ende alles über den einen Port des Backends (8080). # # Der eingebaute SwyxTray-Mock (/ws und /api/mock/**) läuft NICHT mit: seine # Bohnen hängen am Spring-Profil "mock", und das setzt dieses Image nicht. # # Die beiden Build-Stufen laufen bewusst auf $BUILDPLATFORM, also nativ auf dem # bauenden Rechner: JS-Bundle und Jar sind architekturunabhängig. Nur die # Laufzeitstufe nutzt die Zielarchitektur. # --------------------------------------------------------------------------- # 1. Frontend bauen (Vite -> frontend/dist) # --------------------------------------------------------------------------- FROM --platform=$BUILDPLATFORM node:22-alpine AS frontend WORKDIR /build # Erst die Abhängigkeiten, damit diese Schicht im Cache bleibt, solange sich # package.json/package-lock.json nicht ändern. COPY frontend/package.json frontend/package-lock.json ./ RUN npm ci COPY frontend/ ./ # VITE_WS_URL nur setzen, wenn die Adresse fest ins Bundle soll; im Normalfall # kommt sie zur Laufzeit aus /api/config (siehe README). ARG VITE_WS_URL RUN npm run build # --------------------------------------------------------------------------- # 2. Backend bauen (Spring Boot 4.1, Java 21) inkl. Frontend als static/ # --------------------------------------------------------------------------- FROM --platform=$BUILDPLATFORM maven:3.9-eclipse-temurin-21 AS backend WORKDIR /build # Abhängigkeiten vorab auflösen – eigene Schicht, unabhängig vom Quelltext. COPY backend/pom.xml ./ RUN --mount=type=cache,target=/root/.m2 \ mvn -B -q dependency:go-offline COPY backend/src ./src COPY --from=frontend /build/dist/ ./src/main/resources/static/ RUN --mount=type=cache,target=/root/.m2 \ mvn -B -DskipTests clean package \ && cp target/*.jar /build/app.jar # --------------------------------------------------------------------------- # 3. Whisper-Modell einmalig ins Image laden, unabhängig von der Zielarchitektur. # --------------------------------------------------------------------------- FROM --platform=$BUILDPLATFORM python:3.12-slim-bookworm AS whisper-model ENV HF_HUB_DISABLE_TELEMETRY=1 RUN pip install --no-cache-dir huggingface-hub==2.3.0 ARG WHISPER_MODEL_REPO=mobiuslabsgmbh/faster-whisper-large-v3-turbo ARG WHISPER_MODEL_REVISION=0a363e9161cbc7ed1431c9597a8ceaf0c4f78fcf RUN python -c 'import os; from huggingface_hub import snapshot_download; snapshot_download(os.environ["WHISPER_MODEL_REPO"], revision=os.environ["WHISPER_MODEL_REVISION"], local_dir="/models/whisper", allow_patterns=["*.json", "model.bin", "vocabulary.*", "README.md"])' \ && rm -rf /models/whisper/.cache # --------------------------------------------------------------------------- # 4. Java + lokaler Whisper-Worker (glibc für CTranslate2/PyAV statt Alpine). # --------------------------------------------------------------------------- FROM eclipse-temurin:21-jre-noble AS runtime RUN apt-get update \ && apt-get install -y --no-install-recommends python3 python3-venv libgomp1 curl tini \ && rm -rf /var/lib/apt/lists/* \ && python3 -m venv /opt/whisper ENV PATH="/opt/whisper/bin:$PATH" COPY transcription/requirements.txt /app/transcription/requirements.txt RUN pip install --no-cache-dir -r /app/transcription/requirements.txt && pip check # Nicht als root laufen. RUN groupadd --gid 10001 swyx \ && useradd --uid 10001 --gid swyx --create-home swyx \ && mkdir -p /data/transcriptions \ && chown -R swyx:swyx /data WORKDIR /app COPY --from=backend --chown=swyx:swyx /build/app.jar ./app.jar COPY --from=whisper-model /models/ ./models/ COPY transcription/service.py transcription/summary.py ./transcription/ COPY --chmod=755 docker/entrypoint.sh ./entrypoint.sh USER swyx EXPOSE 8080 VOLUME ["/data/transcriptions"] # Adresse der SwyxTray-App zur Laufzeit überschreibbar (Spring Relaxed Binding): # -e APP_WEBSOCKET_HOST=127.0.0.1 -e APP_WEBSOCKET_PORT=17654 # -e APP_WEBSOCKET_PATH=/ws -e APP_WEBSOCKET_SECURE=false # # SPRING_PROFILES_ACTIVE bleibt bewusst leer – der SwyxTray-Mock bliebe sonst # beim Ausrollen erreichbar. Für eine Vorführung ohne Telefonanlage lässt er # sich einzeln einschalten: -e SPRING_PROFILES_ACTIVE=mock ENV JAVA_OPTS="-XX:MaxRAMPercentage=25.0" \ APP_TRANSCRIPTION_BASE_URL=http://127.0.0.1:8765 \ WHISPER_MODEL_PATH=/app/models/whisper \ WHISPER_DATA_DIR=/data/transcriptions \ WHISPER_THREADS=4 \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ HF_HUB_OFFLINE=1 \ HF_HUB_DISABLE_TELEMETRY=1 \ TOKENIZERS_PARALLELISM=false HEALTHCHECK --interval=30s --timeout=5s --start-period=90s --retries=3 \ CMD curl -fsS --max-time 2 http://127.0.0.1:8080/api/config >/dev/null \ && curl -fsS --max-time 2 http://127.0.0.1:8765/health >/dev/null || exit 1 ENTRYPOINT ["/usr/bin/tini", "--", "/app/entrypoint.sh"]