# Das Abbild: ein Prozess, eine Platte, sonst nichts. # # NODE 22 IST FESTGENAGELT, und das ist kein Zufall. better-sqlite3 ist ein # nativer Baustein und laedt beim Installieren eine fertige Binaerdatei # herunter (`prebuild-install || node-gyp rebuild`). Fuer 11.10.0 gibt es die # nur fuer diese ABI-Versionen: # # Node 20 -> ABI 115 vorhanden # Node 22 -> ABI 127 vorhanden <- wir # Node 24 -> ABI 137 FEHLT # # Wer auf 24 hochzieht, faellt auf node-gyp zurueck, und das braucht python3, # make und g++ im Abbild. Der Bau bricht dann ab -- laut und sofort, immerhin. # Vor einem Versionssprung also nachsehen, ob es die ABI als Binaerdatei gibt: # https://github.com/WiseLibs/better-sqlite3/releases # # bookworm-slim und nicht alpine: musl-Binaerdateien gibt es zwar auch, aber # der glibc-Weg ist der ausgetretene. Das Abbild ist dafuer groesser -- bei # einem Container auf dem eigenen Server ist das der guenstigere Tausch. # ------------------------------------------------------------ Abhaengigkeiten FROM node:22-bookworm-slim AS deps WORKDIR /app COPY package.json package-lock.json ./ # Nur was der Betrieb braucht. Hier laeuft prebuild-install und holt die # native Binaerdatei fuer diese Node-Version. RUN npm ci --omit=dev # --------------------------------------------------------------- Oberflaeche # Eigene Stufe, damit ein geaendertes Dashboard nicht den Server neu uebersetzt # und umgekehrt. Was hier entsteht, ist reines HTML/CSS/JS -- im Betrieb laeuft # davon nichts, es wird nur ausgeliefert. FROM node:22-bookworm-slim AS web WORKDIR /ui COPY ui/package.json ui/package-lock.json ./ # Hier bewusst OHNE --omit=dev: vite und tsc sind devDependencies, und ohne sie # gibt es nichts zu bauen. RUN npm ci COPY ui/ ./ # vite.config.ts schreibt nach ../web, also /web. Wer das dort aendert, aendert # die Zeile im Betriebsabbild mit. RUN npm run build # -------------------------------------------------------------------- Bauen FROM node:22-bookworm-slim AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY tsconfig.json ./ COPY src ./src RUN npm run build # ------------------------------------------------------------------ Betrieb FROM node:22-bookworm-slim AS runtime ENV NODE_ENV=production WORKDIR /app # ffmpeg -- fuer EIN EINZIGES Bild je Video, und trotzdem die groesste einzelne # Zutat dieses Abbilds. # # DIE ABWAEGUNG, ausgesprochen: eine Kachel ohne Vorschau ist ein Fragezeichen. # Bei 3600 Bildern faellt das nicht auf, bei einer Handvoll Clips ist die # Galerie dann eine Liste von Dateinamen -- und dafuer braucht es keine # Galerie. Ein Standbild kann sharp nicht liefern: libvips dekodiert kein # Video, das ist kein Einstellungsproblem. # # Gerechnet wird NUR beim Hochladen und beim Nachziehen, nie beim Ausliefern. # ffmpeg laeuft also nicht im Anfrageweg. # # WARUM NICHT `apt-get install ffmpeg` -- nachgemessen und wieder verworfen: # Debians Paket schaltet jeden Filter frei und zieht die Abhaengigkeiten dafuer # mit. Das Abbild wuchs von 460 MB auf 1,11 GB, und die groessten Brocken # waren libllvm15 (114 MB), Mesa samt libgl1-mesa-dri (25 MB), libz3 (22 MB) # und libflite1 (27 MB, ein SPRACHSYNTHESIZER). Alles fuer Filter, die ein # Dienst ohne Bildschirm nie anfasst; --no-install-recommends half nicht, denn # es sind harte Abhaengigkeiten. # # Also die statischen Binaerdateien, auf eine Fassung FESTGENAGELT. Der Preis # dafuer ist ehrlich zu benennen: Aktualisierungen kommen nicht mehr ueber apt, # sondern nur, wenn jemand diese Zeile anfasst. Vertretbar, weil ffmpeg hier # nur eigene, bereits abgelegte Dateien anfasst und nicht am Netz haengt. # NUR ffmpeg, NICHT ffprobe: das zweite Werkzeug waegt noch einmal 135 MB und # haette hier eine einzige Aufgabe gehabt -- die Laenge zu nennen. Die steht # auch im Kopf, den ffmpeg beim Oeffnen druckt; nachgemessen an beiden Dateien, # auf zwei Nachkommastellen identisch (siehe medienDauer in storage.ts). COPY --from=mwader/static-ffmpeg:7.1 /ffmpeg /usr/local/bin/ COPY --from=deps /app/node_modules ./node_modules COPY --from=build /app/dist ./dist COPY package.json ./ # Die gebaute Oberflaeche. Der Dienst liefert sie aus, wenn sie da ist, und # sagt sonst, dass sie fehlt (siehe index.ts) -- diese Auskunft bleibt als # Netz, falls die Stufe oben je leer ausgeht. COPY --from=web /web ./web # Ein Ordner fuer Bilder UND media.db, damit eine Sicherung aus genau einem # Kopiervorgang ueber ein Volume besteht. ENV DATA_DIR=/data RUN mkdir -p /data && chown -R node:node /data /app # Nicht als root. Der Dienst nimmt Dateien von aussen entgegen und schreibt sie # auf die Platte -- das ist die Stelle, an der man das nicht will. USER node ENV PORT=8080 EXPOSE 8080 # Ohne curl im Abbild: Node kann das selbst, fetch ist ab 18 eingebaut. HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||8080)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" CMD ["node", "dist/index.js"]