# syntax=docker/dockerfile:1 # Die Oberfläche zuerst, in einem eigenen Abschnitt: sie braucht Node, der # Server nicht. So landet Node nie im fertigen Image. FROM --platform=$BUILDPLATFORM node:22-alpine AS web WORKDIR /web COPY web/app/package.json web/app/package-lock.json ./ RUN npm ci COPY web/app/ ./ RUN npm run build # Gebaut wird auf der Architektur des Bauwirts, übersetzt wird für die des # Ziels — `dotnet publish -a` kann das. Ohne diese Trennung liefe der Bau für # arm64 unter Emulation und dauerte ein Vielfaches. FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG TARGETARCH WORKDIR /src # Erst die Projektdateien, dann wiederherstellen: so bleibt die # Paket-Wiederherstellung in der Ebenenzwischenablage, solange sich keine # Abhängigkeit ändert. Ohne diesen Schritt lädt jeder Bau alles neu. # `--parents` behält die Verzeichnisse bei. **Ein Muster statt einer Liste**, # weil eine Liste bei jedem neuen Projekt vergessen wird — und dann scheitert # erst der Bau im Container, nicht der auf dem Arbeitsrechner. COPY --parents server/src/*/*.csproj ./ RUN dotnet restore server/src/Flaro.Api/Flaro.Api.csproj -a $TARGETARCH COPY server/ ./server/ RUN dotnet publish server/src/Flaro.Api/Flaro.Api.csproj -a $TARGETARCH -c Release -o /app --no-restore FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime # ffmpeg und die Treiber für Hardware-Beschleunigung. # # **Als eigenes Programm, nicht als Bibliothek.** Flaro ruft es als Prozess # auf; damit bleiben die Lizenzen getrennt (ffmpeg steht unter GPL) und ein # Absturz dort nimmt den Server nicht mit. # # `mesa-va-drivers` bedient AMD und ältere Intel, `intel-media-va-driver` die # neueren Intel — beide über VAAPI an `/dev/dri`. Welcher greift, entscheidet # sich zur Laufzeit danach, was durchgereicht wurde. # # **Die Intel-Treiber nur auf amd64.** Es gibt sie für arm64 nicht, und ein # unbedingtes `apt-get install` liess den Bau dort mit Code 100 abbrechen — # eine Meldung, die nicht verrät, welches der sechs Pakete fehlt. ARG TARGETARCH RUN apt-get update \ && apt-get install -y --no-install-recommends \ ffmpeg libva2 libva-drm2 mesa-va-drivers curl \ libchromaprint-tools \ && if [ "$TARGETARCH" = "amd64" ]; then \ apt-get install -y --no-install-recommends intel-media-va-driver i965-va-driver; \ fi \ && rm -rf /var/lib/apt/lists/* # **Die Verzeichnisse müssen hier angelegt werden, nicht erst beim Start.** # Docker legt den Einhängepunkt eines frischen benannten Volumes sonst als # `root` an und übernimmt für ein leeres Volume die Rechte aus dem Image — # gibt es das Verzeichnis im Image nicht, erbt es `root:root`, und der erste # Start scheitert für jeden, der kein Bind Mount benutzt. RUN mkdir -p /config /cache /transcode && chown -R 1000:1000 /config /cache /transcode # **Kein root.** Die Kennung 1000 ist die des ersten Benutzers auf fast jedem # Linux und auf den üblichen NAS-Geräten — wessen Dateien anders gehören, setzt # in der Compose-Datei `user: ":"`. Das ist Docker-Bordmittel und # braucht weder ein Startskript noch root im Container. USER 1000:1000 # **Kein eigenes ASPNETCORE_URLS.** Das Basisimage setzt seit .NET 8 selbst # ASPNETCORE_HTTP_PORTS=8080; beides zusammen ergab bei jedem Start die # Warnung „Overriding HTTP_PORTS '8080'". Zwei Wege zum selben Port sind # einer zu viel. ENV FLARO_DATA=/config \ FLARO_CACHE=/cache \ FLARO_TRANSCODE=/transcode \ DOTNET_gcServer=0 # **Welches Abbild das hier ist.** # # Sechs Zeichen, bei jedem Bau neu gewürfelt — auch dann, wenn am Quelltext # nichts anders ist. Die Version sagt, welcher Stand gemeint ist; diese # Kennung sagt, welches Abbild wirklich läuft. # # Das ist keine Spielerei: zwei Funktionen standen nachweislich im Quelltext # und waren auf dem Server trotzdem nicht zu sehen. Ob dort ein älteres # Abbild lief, war nicht feststellbar, und die Fehlersuche lief einen halben # Tag ins Leere. Seitdem beantwortet ein Blick die erste aller Fragen. # # Ganz unten, weil jede Änderung daran alle folgenden Schichten neu baut — # und das sind hier nur noch die beiden COPY-Zeilen. ARG FLARO_BUILD=DEV ENV FLARO_BUILD=${FLARO_BUILD} WORKDIR /app COPY --from=build /app . COPY --from=web /web/dist ./wwwroot EXPOSE 8080 # **Eine Gesundheitsprüfung, die den Server fragt — nicht die Laufzeit.** # # Ohne sie weiss Docker nur, dass ein Prozess läuft. Die Oberfläche eines NAS # zeigt dann minutenlang „wird bereitgestellt", während der Server längst # antwortet, und niemand kann unterscheiden, ob er noch wandert oder hängt. # # `/health` und nicht `dotnet --info`: letzteres sagt nur, dass die Laufzeit # da ist, und das war sie auch, als der Server noch migrierte. # # 60 Sekunden Anlaufzeit, weil die Migrationen beim ersten Start einer # gefüllten Datenbank länger brauchen als ein Startvorgang sonst. HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ CMD curl -fsS http://localhost:8080/health || exit 1 VOLUME ["/config", "/cache", "/transcode"] ENTRYPOINT ["dotnet", "Flaro.Api.dll"]