Table of Contents
- docker-build-push
- Warum image-owner Pflicht ist und registry nicht
- Warum push eine Zeichenkette ist und kein Boolean
- Der Prüfmodus und sein Preis
- Warum zwei verschiedene Zugänge
- Warum es keinen env-Block auf Workflow-Ebene gibt
- Die Vorgaben der Versionsangaben sind absichtlich unecht
- Der Runner braucht ein Docker-Socket, und das Label entscheidet darüber
- Bekannte Schwachstellen
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.