1
docker build push
Justin Klein edited this page 2026-10-01 22:43:52 +00:00

docker-build-push

.gitea/workflows/docker-build-push.yml — baut ein Docker-Image und schiebt es als Multi-Arch-Manifest in eine Registry. Die Eingabe-Tabelle, das Aufrufbeispiel und die Voraussetzungen stehen im README; diese Seite begründet, warum der Baustein so aussieht.

Warum image-owner Pflicht ist und registry nicht

Der Eigentümer ist je Projekt verschieden, die Registry praktisch immer dieselbe. Deshalb hat registry eine Vorgabe und image-owner keine.

Das ist keine Geschmacksfrage: Der Eigentümer war früher fest verdrahtet, und die Folge war, dass die Images aller aufrufenden Projekte unter demselben fremden Konto landeten. Ein geratener Eigentümer scheitert nicht, er schiebt das Image woandershin — und das fällt erst auf, wenn jemand es dort sucht, wo es hingehört. Die Regel steht daher allgemein auf Home: Ein Eigentümer-Segment wird nie geraten.

Warum push eine Zeichenkette ist und kein Boolean

Weil ein boolescher workflow_call-Eingang auf dieser Forge-Fassung nicht ankommt — nicht einmal seine Vorgabe. Der Baustein baute dadurch immer im Prüfmodus und meldete dabei Erfolg. Die Messung, die Läufe und die Eingrenzung stehen auf Gemessenes-Verhalten.

Zwei Dinge, die man dazu wissen muss:

Die Anführungszeichen sind nicht kosmetisch. push: false ohne sie ist im YAML ein Boolean und kommt nicht an; es griffe dann die Vorgabe, und der Lauf veröffentlichte trotz push: false. Richtig ist push: 'false'.

Die Bedingung musste bei der Umstellung nicht angefasst werden. Sie lautet inputs.push && inputs.push != 'false' und war von Anfang an für beide Schreibweisen gebaut: inputs.push allein wäre bei der Zeichenkette 'false' wahr, weil nichtleere Zeichenketten truthy sind; inputs.push == 'true' wäre beim echten Boolean true falsch, weil ein Vergleich über Typgrenzen über Zahlen läuft und 'true' dabei zu NaN wird. Die Kombination deckt alle vier Fälle ab.

Wer das beim nächsten Aufräumen „korrekt" auf boolean zurückstellt, holt den Fehler still zurück: Es wird nichts veröffentlicht, und der Lauf bleibt grün. Darum steht die Begründung auch als Kommentar an der Eingabe selbst.

Der Prüfmodus und sein Preis

Mit abgeschaltetem Push baut der Baustein nur. Der Zweck ist, dass die CI eines Projekts dieselbe Bau-Logik benutzt wie das Deployment, statt sie zu duplizieren.

Der Preis hängt an einer Kopplung, die nicht offensichtlich ist: Ohne Push wird der 1Password-Schritt übersprungen, und aus demselben Schritt stammt der Checkout-Token. Der Checkout fällt dann auf den Standard-Token des Jobs zurück. Der genügt für das ausführende Repository, nicht aber für Submodule in eigenen privaten Repositories — deren Klon scheitert mit Repository not found.

Daraus folgt: Projekte mit privaten Submodulen können den Prüfmodus nicht verwenden. Für Projekte ohne Submodule ist er unproblematisch.

Warum zwei verschiedene Zugänge

Der Token, den die Forge jedem Job stellt, taugt für zwei Dinge nicht: Er gilt nur für das ausführende Repository, während die Submodule in eigenen privaten Repositories liegen, und die Container-Registry lehnt ihn ab (container.Authenticate antwortet mit 401). Beides deckt je ein eigener Zugang ab, gepflegt in 1Password und zur Laufzeit über den Connect-Server im Cluster geholt. In der Forge liegt nur dessen Zugang.

Benutzername und Token für die Registry kommen aus einem Eintrag, damit sie zwangsläufig zusammenpassen — die Registry prüft den Token gegen genau das Konto, das im Feld username steht.

Warum es keinen env-Block auf Workflow-Ebene gibt

env auf Workflow-Ebene wird ausgewertet, bevor es einen inputs-Kontext gibt. Registry und Eigentümer standen dort früher; als sie Eingaben wurden, konnten sie nicht bleiben. Sie werden deshalb an den beiden Stellen gelesen, an denen der Kontext sicher vorhanden ist: im Login-Schritt und in der Tag-Liste.

Dieselbe Falle gilt für jeden künftigen Baustein hier.

Die Vorgaben der Versionsangaben sind absichtlich unecht

Die vier Versionsangaben dienen ausschließlich der Tag-Liste. Ihre Vorgabe ist ein sichtbar unechter Wert und nicht leer, weil die Tag-Liste auch ohne Push gebildet wird: Die Bau-Action gibt sie als --tag an den Build weiter, und ein leerer Wert ergäbe <registry>/<owner>/<name>: — der Build scheiterte an invalid reference format.

Der Runner braucht ein Docker-Socket, und das Label entscheidet darüber

Der Baustein läuft auf einem Runner mit einem eigenen Label, nicht auf dem Standard-Label. Gemessen in jlab-cluster/helm-charts: Auf dieser Instanz hat der Standard-Runner kein Docker-Socket, der mit dem Docker-Label hat eines. Dasselbe Label bedeutet auf den beiden Forges, auf denen dieses Setup lebt, verschiedene Maschinen.

Läuft der Daemon des Runners rootless, liegt sein Socket nicht unter /var/run/docker.sock, sondern unter /run/user/<uid>/docker.sock. Der Pfad kommt deshalb aus einer Variablen des aufrufenden Repositories; ist sie nicht gesetzt, bleibt sie leer und Docker nutzt seinen Standard. Ein eigener, rein informativer Schritt gibt aus, welches Socket im Job-Container ankommt — weil das beim Scheitern des Buildx-Starts immer die erste Frage ist.

Bekannte Schwachstellen

Festgehalten, damit sie nicht erneut entdeckt werden müssen; alle drei in Issue #5.

Der Buildx-Action-Verweis wandert. Er zeigt auf einen Zweig statt auf eine Hauptversion. Das widerspricht genau der Regel, die das README von Aufrufern verlangt — und hat dieselbe Folge: Eine Änderung dort ändert das Verhalten aller Projekte, ohne dass hier etwas angefasst wurde. Siehe Gemessenes-Verhalten.

Die Beschreibung von platforms behauptet etwas Falsches. Sie sagt, die Bau-Stufen aller Dockerfiles seien auf die Bau-Plattform gepinnt, eine zusätzliche Plattform fügte also nur eine Laufzeitstufe hinzu. Für das Dockerfile des ersten Abnehmers stimmt das nicht, und zwar bewusst nicht: Tailwind, lightningcss und der Next-Compiler bringen plattformgebundene Binärmodule mit, ein Cross-Build baute die falsche Architektur ein. Eine zusätzliche Plattform bedeutet dort einen vollständigen zweiten Kompilierlauf unter Emulation. Gemessen: 72 Sekunden für eine Plattform gegen 417 Sekunden für zwei. Als allgemeine Aussage in einem geteilten Baustein führt der Satz in die Irre; er hat bei einem Abnehmer zu einer Fehleinschätzung beigetragen.

Die Tag-Liste erzeugt einen Tag doppelt. Bei einem dreiteiligen Versionsschema ergeben die vollständige Version und die aus Major, Minor und Patch zusammengesetzte denselben Wert. Harmlos — Docker pusht denselben Tag zweimal —, aber beim Nachzählen verwirrend. Nebenbei: Bei einem Kalenderschema sind die mitwandernden Tags „neuestes Image aus diesem Jahr" beziehungsweise „aus diesem Monat" von zweifelhaftem Nutzen; bei SemVer ergeben sie Sinn.