Pre-built dist in Git committen: Warum dieser kontroverse Ansatz eure Deployments beschleunigt

Von Roland Golla
0 Kommentar
Schmelzender Git-Ordner fließt in Docker-Container, surreale Dalí-Landschaft

Ihr kennt das bestimmt: Der CI/CD-Server läuft seit einer gefühlten Ewigkeit, installiert zum hundertsten Mal die gleichen npm-Pakete, kompiliert TypeScript und bündelt Webpack – obwohl sich am Code seit dem letzten Build eigentlich gar nichts geändert hat. Irgendwann fragt ihr euch: Muss das wirklich so sein? Bei Never Code Alone haben wir nach über 15 Jahren Spezialisierung auf Softwarequalität, Open Source und Remote Consulting einen Ansatz wiederentdeckt, der in der Community oft als Tabu gilt, aber erstaunlich viele Probleme löst: Das Committen von vorgebauten dist-Ordnern direkt ins Repository.

Warum die Standard-Empfehlung nicht immer passt

Die allermeiste Dokumentation im Netz erklärt euch, dass der dist-Ordner niemals ins Git gehört. Das hat gute Gründe: Repository-Bloat, Merge-Konflikte bei generierten Dateien, und die philosophische Überzeugung, dass ein Repository nur Quellcode enthalten sollte. Doch in der Praxis sehen wir immer wieder Teams, die unter dieser Doktrin leiden, ohne dass jemand hinterfragt, ob die Nachteile wirklich auf ihren konkreten Use Case zutreffen.

Die zentrale Frage lautet nicht „Was sagt die Best Practice?“, sondern „Was braucht unser Projekt konkret?“. Wenn ihr eine moderne JavaScript-Anwendung mit hunderten npm-Dependencies habt, kann ein einzelner Build-Vorgang mehrere Minuten dauern. Multipliziert das mit jedem Deployment, jedem Feature-Branch und jedem Rollback-Versuch. Die Rechnung geht schnell nicht mehr auf.

Was passiert eigentlich, wenn ihr den dist-Ordner aus der .gitignore entfernt und die gebauten Dateien einfach mitcommittet? Zunächst einmal: Nichts Schlimmes. Euer Repository wird etwas größer, aber dafür bekommt ihr etwas Wertvolles zurück – Vorhersagbarkeit. Was ihr committet, ist exakt das, was deployed wird. Kein Build-Server, der zwischenzeitlich eine andere Node-Version hat. Keine npm-Registry, die gerade nicht erreichbar ist. Keine subtilen Unterschiede zwischen lokaler und CI-Umgebung.

Welche konkreten Vorteile bringt das Committen von Build-Artefakten

Der offensichtlichste Vorteil ist Geschwindigkeit. Wenn euer Deployment-Prozess den Build-Schritt überspringen kann, spart ihr bei jeder Auslieferung Zeit. Bei Teams, die mehrmals täglich deployen, summiert sich das schnell auf Stunden pro Woche. Doch der eigentliche Gewinn liegt tiefer: Ihr eliminiert eine komplette Fehlerquelle.

In unserer Consulting-Praxis haben wir unzählige Fälle erlebt, in denen Deployments scheiterten, weil der Build-Server nicht identisch zur Entwicklungsumgebung konfiguriert war. Unterschiedliche npm-Versionen, fehlende native Dependencies, Memory-Limits bei komplexen Builds – die Liste ist lang. Mit vorgebauten Artefakten im Repository verschwinden all diese Probleme.

Die Dockerfile wird zum Dreizeiler: FROM nginx:alpine, COPY dist /usr/share/nginx/html, EXPOSE 80. Keine mehrstufigen Builds mehr, keine Installation von Node.js im Production-Image, keine npm ci && npm run build Zeremonie. Das Ergebnis ist ein kleineres, schnelleres und sichereres Container-Image.

Wann ist dieser Ansatz die richtige Wahl für euer Projekt

Nicht jedes Projekt profitiert gleichermaßen. Der Ansatz macht besonders Sinn bei Frontend-Anwendungen mit statischem Output, also Single-Page-Applications, statisch generierten Websites oder Bibliotheken, die als Bundle ausgeliefert werden. Weniger geeignet ist er für Backend-Services mit vielen Laufzeit-Dependencies oder Projekte mit sehr großen Build-Artefakten.

Ein guter Indikator: Wenn eure gebauten Dateien komprimiert unter zehn Megabyte liegen und sich bei den meisten Commits nur minimal ändern, ist der Repository-Overhead vernachlässigbar. Bei Projekten mit Gigabyte-großen Assets oder generiertem Code, der sich bei jedem Build komplett ändert, überwiegen die Nachteile.

Auch die Team-Struktur spielt eine Rolle. Wenn mehrere Entwickler parallel an Features arbeiten und häufig Branches mergen, können Konflikte in generierten Dateien nervig werden. Die Lösung: Etabliert eine klare Konvention, dass der dist-Ordner vor dem Merge immer neu gebaut wird. Viele Teams automatisieren das mit einem Pre-Commit-Hook.

Wie unterscheidet sich dieser Ansatz vom klassischen CI/CD-Build

Im klassischen Setup ist der Build ein integraler Teil der Pipeline. Der Code wird gepusht, der CI-Server checkt aus, installiert Dependencies, führt den Build durch, testet und deployt. Jeder dieser Schritte kann fehlschlagen, und oft sind es die Build-Schritte, die am meisten Zeit fressen und am häufigsten mysteriöse Fehler produzieren.

Mit vorgebauten Artefakten verschiebt ihr den Build-Schritt auf den Entwicklerrechner. Das klingt zunächst wie ein Rückschritt, ist aber bei genauerer Betrachtung oft die bessere Wahl. Entwickler haben die volle Kontrolle über ihre Build-Umgebung, können Probleme sofort debuggen und committen erst, wenn der Build erfolgreich war.

Die CI-Pipeline reduziert sich auf das Wesentliche: Tests ausführen, Linting prüfen, und wenn alles grün ist, die bereits gebauten Artefakte deployen. Das macht die Pipeline nicht nur schneller, sondern auch deutlich einfacher zu verstehen und zu warten.

Welche Best Practices gelten für das Arbeiten mit committeten Build-Artefakten

Der wichtigste Grundsatz: Konsistenz. Wenn ihr euch für diesen Ansatz entscheidet, muss das gesamte Team die gleiche Vorgehensweise befolgen. Ein Build-Befehl sollte deterministisch sein, also auf allen Maschinen das gleiche Ergebnis liefern. Das erreicht ihr durch festgepinnte Dependency-Versionen in package-lock.json oder yarn.lock.

Richtet einen Git-Hook ein, der vor jedem Commit den Build triggert und die geänderten Dateien automatisch stagt. So vergisst niemand, den dist-Ordner zu aktualisieren. Ein einfaches Pre-Commit-Script reicht oft aus: npm run build && git add dist/.

Dokumentiert die Entscheidung im README oder in eurer Entwickler-Dokumentation. Neue Team-Mitglieder werden sonst verwundert sein, warum der dist-Ordner nicht in der .gitignore steht. Erklärt die Gründe und die erwartete Arbeitsweise.

Was bedeutet das konkret für eure Docker-Container

Die Vereinfachung der Dockerfile ist einer der greifbarsten Vorteile. Statt eines mehrstufigen Builds mit Builder-Stage und Production-Stage braucht ihr nur noch eine Stage, die die fertigen Dateien kopiert. Das reduziert nicht nur die Komplexität, sondern auch die Build-Zeit des Images erheblich.

Ein typisches Dockerfile für eine React-Anwendung mit vorgebautem dist könnte so aussehen:

FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Vergleicht das mit einer klassischen mehrstufigen Build-Variante, die Node.js installiert, npm ci ausführt, den Build triggert und dann die Artefakte in ein neues Image kopiert. Der Unterschied in Komplexität und Fehleranfälligkeit ist enorm.

Wie geht ihr mit Merge-Konflikten in generierten Dateien um

Das ist die häufigste Sorge, die wir hören, und sie ist berechtigt. Minifizierte JavaScript-Dateien oder gebündelte CSS-Dateien produzieren bei Konflikten unlesbare Diffs. Die gute Nachricht: In der Praxis ist das weniger problematisch als befürchtet.

Der Trick liegt in der Merge-Strategie. Wenn ein Konflikt im dist-Ordner auftritt, resolved ihr ihn nicht manuell, sondern baut einfach neu: Merge abschließen, npm run build, committen. Da die generierten Dateien deterministisch aus dem Quellcode entstehen, ist der korrekte Zustand immer reproduzierbar.

Für Teams, die häufig mergen, empfehlen wir eine Git-Konfiguration, die den dist-Ordner als binär behandelt. Das verhindert, dass Git versucht, zeilenweise Merges durchzuführen, und zwingt euch stattdessen zur sauberen Neu-Generierung.

Welche Alternativen gibt es zu diesem Ansatz

Falls euch das vollständige Committen der Build-Artefakte zu weit geht, gibt es Zwischenlösungen. Eine beliebte Variante ist das Committen in einen separaten Branch – der main-Branch enthält nur Quellcode, während ein build- oder artifacts-Branch die gebauten Dateien vorhält. Deployment-Pipelines referenzieren dann den entsprechenden Branch.

Eine andere Option sind Git-Releases mit angehängten Assets. Bei jedem Tag wird der Build als ZIP-Datei an das Release angehängt. Das hält den Repository-Baum sauber, erfordert aber zusätzliche Infrastruktur für das Artifact-Handling.

Für Enterprise-Umgebungen bieten sich dedizierte Artifact-Repositories wie JFrog Artifactory oder AWS CodeArtifact an. Diese sind für große Binärdateien optimiert und bieten Features wie Versionierung, Access Control und CDN-Distribution. Der Overhead lohnt sich aber erst bei größeren Projekten.

Welche Sicherheitsaspekte müsst ihr beachten

Ein oft übersehener Vorteil: Mit vorgebauten Artefakten eliminiert ihr das Risiko von Supply-Chain-Attacken während des Builds. Wenn der Build auf eurem lokalen Rechner stattfindet, kontrolliert ihr genau, welche Pakete installiert werden. Ein kompromittiertes npm-Paket kann euren CI-Server nicht mehr während des Builds infiltrieren.

Achtet darauf, keine sensitiven Daten in den Build einzubaken. Environment-Variablen für API-Keys oder Secrets sollten zur Laufzeit injiziert werden, nicht zur Build-Zeit. Überprüft eure gebauten Dateien vor dem Commit auf versehentlich eingebettete Credentials.

Die Nachvollziehbarkeit verbessert sich ebenfalls: Jeder Commit enthält den exakten Stand der Anwendung, wie er deployed wird. Ihr könnt jederzeit nachvollziehen, welcher Code zu welchem Zeitpunkt live war, ohne einen historischen Build reproduzieren zu müssen.

Wie migriert ihr ein bestehendes Projekt auf diesen Ansatz

Die Migration ist überraschend einfach. Entfernt zunächst den dist-Ordner aus eurer .gitignore. Führt dann einen vollständigen Build durch und committet das Ergebnis. Passt eure CI-Pipeline an, indem ihr den Build-Schritt entfernt oder ihn nur noch für Tests nutzt. Vereinfacht abschließend eure Dockerfile.

Testet die neue Pipeline gründlich in einer Staging-Umgebung, bevor ihr sie für Production aktiviert. Achtet besonders darauf, dass die gebauten Dateien korrekt serviert werden und keine Pfad-Probleme entstehen.

Kommuniziert die Änderung an euer Team und aktualisiert eure Dokumentation. Der größte Stolperstein ist oft nicht technischer Natur, sondern die Gewöhnung an einen neuen Workflow.

Euer nächster Schritt

Das Committen von Build-Artefakten ins Repository ist kein Allheilmittel, aber für viele Projekte eine pragmatische Lösung, die Deployment-Prozesse erheblich vereinfacht. Der Schlüssel liegt wie so oft darin, die richtige Entscheidung für euren konkreten Kontext zu treffen.

Was ihr jetzt tun solltet: Analysiert eure aktuelle Build-Pipeline. Wie lange dauert ein durchschnittlicher Build? Wie oft scheitern Deployments wegen Build-Problemen? Wenn die Antworten frustrierend sind, könnte der hier beschriebene Ansatz eine Überlegung wert sein.

Ihr wollt eure CI/CD-Prozesse optimieren oder habt Fragen zur richtigen Strategie für euer Projekt? Bei Never Code Alone haben wir über 15 Jahre Erfahrung in Softwarequalität, Open Source und Remote Consulting. Wir helfen euch von der ersten Analyse bis zur fertigen Implementierung.

Schreibt uns einfach eine E-Mail an roland@nevercodealone.de – wir freuen uns auf eure Herausforderung!

Never Code Alone – Gemeinsam für bessere Software-Qualität!

0 Kommentar

Tutorials und Top Posts

Gib uns Feedback

Diese Seite benutzt Cookies. Ein Akzeptieren hilft uns die Seite zu verbessern. Ok Mehr dazu