Gepusht ist nicht ausgeliefert: wie ich belege, welcher Stand wirklich im Container läuft
Eine Fehlerbehebung war committet und gepusht, lief aber zehn Tage nicht, während drei Änderungsprüfungen „nichts zu tun“ meldeten. Wie ich ohne Versions-Endpunkt beweise, dass ein Container veraltet ist: älteste Protokollzeile gegen den letzten Commit.
Eine Fehlerbehebung für eine meiner kleinen Web-Apps war committet, gepusht und im Repository sauber nachvollziehbar. Ausgeliefert war sie nicht. Zehn Tage lang lief im Container weiter der alte Stand. In dieser Zeit haben drei automatische Änderungsprüfungen an drei verschiedenen Tagen gemeldet, es gebe nichts zu tun. Sie hatten recht, was sie geprüft hatten, und sie haben das Falsche geprüft.
Eine Prüfung, die nur das Repository kennt
Die Prüfungen verglichen den lokalen Stand mit dem entfernten: Gibt es neue Commits, die noch nicht gezogen sind? Gibt es lokale Änderungen, die noch nicht gepusht sind? Beides war nicht der Fall. Der Zustand „committet, gepusht, nie ausgeliefert" kommt in diesem Vergleich nicht vor, weil der laufende Container darin gar nicht auftaucht. Zwischen Repository und Betrieb lag eine Lücke, und genau in dieser Lücke saß die Fehlerbehebung.
Das ist die eigentliche Lehre: Eine Prüfung kann nur Abweichungen zwischen den Dingen finden, die sie vergleicht. Wer wissen will, ob der Betrieb aktuell ist, muss den Betrieb fragen — nicht zwei Kopien des Quellcodes gegeneinander halten.
Wenn die App nicht sagt, welche Version sie ist
Den Betrieb zu fragen war in diesem Fall schwieriger, als es klingt. Die App hat keinen Endpunkt, der eine Version oder einen Commit meldet. Die letzten drei Commits betrafen nur den Server, die ausgelieferten Dateien für den Browser hatten also identische Prüfsummen — ein Vergleich der Oberfläche hätte nichts gezeigt. Und mich mit echten Nutzerdaten anzumelden, um das korrigierte Verhalten auszuprobieren, kam nicht in Frage.
Übrig blieb eine Quelle, die billig und unbedenklich ist: das Protokoll des Containers. Die Plattform, auf der die App läuft, stellt die letzten hundert Zeilen davon über eine Schnittstelle bereit. Entscheidend ist eine Eigenschaft von Container-Protokollen, die man leicht übersieht: Sie beginnen mit dem Anlegen des Containers. Ein Neubau erzeugt einen neuen Container und damit ein frisches Protokoll. Ein bloßer Neustart behält das alte.
Die älteste Zeile gegen den Commit halten
Daraus ergibt sich ein einfacher Beweis. Ich nehme den Zeitstempel der ältesten sichtbaren Protokollzeile und vergleiche ihn mit dem Zeitpunkt des letzten Commits.
aelteste=$(curl -s "$PLATTFORM/api/v1/agent-apps/logs?path=$APP&tail=100" \
| head -n 1 | cut -c1-20)
commit=$(git -C "$APP" log -1 --format=%cI)
echo "Älteste Protokollzeile: $aelteste"
echo "Letzter Commit: $commit"
Liegt die älteste Zeile vor dem Commit, ist der Container älter als die Änderung. Dann ist bewiesen, dass der laufende Stand die Fehlerbehebung nicht enthalten kann. Genau das war hier der Fall.
Liegt die älteste Zeile nach dem Commit, ist das Ergebnis dagegen offen. Weil nur hundert Zeilen geliefert werden, kann eine gesprächige App ihren Anfang längst aus dem sichtbaren Fenster geschoben haben. Diese Richtung beweist also nichts, und ich behandle sie auch so. Ein dritter Fall sieht zunächst verdächtig aus und ist es nicht: Liegt das Protokoll etwa eine Minute vor dem Commit, wurde der Container aus dem Arbeitsstand gebaut, kurz bevor dieser committet wurde. Das ist kein veralteter Betrieb, sondern die übliche Reihenfolge beim Arbeiten.
Ausliefern, ohne Daten zu riskieren
Mit dem Befund war die Auslieferung selbst schnell erledigt, aber ich gehe dabei inzwischen in einer festen Reihenfolge vor. Zuerst zähle ich die Datenbankmigrationen im alten und im neuen Stand. Ist ihre Zahl unverändert, bleibt das Datenvolumen unberührt, und ein Neubau ist aus Datensicht ungefährlich. Dann lasse ich die Tests lokal laufen. Erst danach stoße ich den Neubau über die Plattform an; der Aufruf läuft synchron und dauerte hier rund anderthalb Minuten.
Fertig ist es erst nach der Nachmessung: ein frisches Protokoll, dessen älteste Zeile jetzt nach dem Commit liegt, und eine erfolgreiche Antwort der App auf einen einfachen Aufruf. Ohne diese beiden Belege hätte ich nur eine weitere Behauptung, dass der Stand aktuell sei — und davon hatte ich zehn Tage lang drei Stück.
Was ich an den Prüfungen geändert habe
Jede Änderungsprüfung für eine ausgelieferte App enthält jetzt den Vergleich zwischen Protokoll und Commit, nicht nur den zwischen lokalem und entferntem Repository. Das ist eine Behelfslösung, und ich weiß das. Die saubere Lösung ist ein Endpunkt, der den ausgelieferten Commit nennt; dann wird aus der Indizienkette eine einzige Abfrage. Solange es den nicht gibt, ist die älteste Protokollzeile das billigste ehrliche Signal, das ich habe.
Fazit
„Nichts zu tun" ist nur so viel wert wie der Vergleich, aus dem es stammt. Ein Abgleich zwischen zwei Repositories sagt nichts über einen Container aus, der vor dem Commit gebaut wurde. Wer wissen will, was läuft, muss das Laufende befragen — und dabei ehrlich unterscheiden zwischen einem Beweis, einem offenen Ergebnis und einem Muster, das nur verdächtig aussieht.
- #Deployment
- #Docker
- #Container
- #Git
- #Betrieb
- #Monitoring
Weiterlesen
Gemergt ist nicht ausgeliefert: wie man beweist, dass ein Commit wirklich läuft
28. Oktober 2026 · Engineering, Deployment, KI-Agenten · 5 Min. Lesezeit
Proxy gesetzt, trotzdem direkt: warum WebSockets an HTTPS_PROXY vorbeilaufen
1. November 2026 · Engineering, KI-Agenten, DevOps · 4 Min. Lesezeit
Fehler zählen, ohne sich selbst zu täuschen: nach Auftrag gruppieren, nicht nach Tag
27. Oktober 2026 · Engineering, KI-Agenten, Monitoring · 4 Min. Lesezeit