Geteilte Docker-Build-and-Push-Pipeline anlegen #2

Merged
jklein merged 2 commits from docker-build-push into main 2026-09-30 16:11:05 +02:00
Member

Fixes #1

Die bisher in einem einzelnen Projekt liegende Build-and-Push-Pipeline zieht als
wiederverwendbarer Workflow nach .gitea/workflows/docker-build-push.yml um —
und wird dabei an drei Stellen von der Herkunft aus einem einzelnen Projekt
gelöst.

1. Das Image-Präfix

IMAGE_OWNER: suer stand fest im env-Block, REGISTRY: git.jk-dev.de
ebenfalls. Unverändert übernommen wäre jedes Image jedes aufrufenden Projekts
unter git.jk-dev.de/suer/… gelandet. Beide sind jetzt Eingaben — mit
bewusster Asymmetrie:

Eingabe Pflicht Vorgabe
image-owner ja keine
registry nein git.jk-dev.de

Der Eigentümer ist je Projekt verschieden und soll nie stillschweigend geraten
werden — genau das war der Fehler des fest verdrahteten suer. Die Registry ist
dagegen praktisch immer dieselbe.

Nicht rückwärtskompatibel: Bestehende Aufrufer müssen image-owner
ergänzen, sonst schlägt der Aufruf an der fehlenden Pflichteingabe fehl. Das
ist gewollt.

Der env-Block auf Workflow-Ebene entfällt damit ersatzlos — sein einziger
verbliebener Eintrag war REGISTRY. Bleiben konnte der Wert dort ohnehin nicht:
env auf Workflow-Ebene wird ausgewertet, bevor es einen inputs-Kontext gibt.
Registry und Eigentümer werden deshalb dort gelesen, wo der Kontext sicher
vorhanden ist — im Login-Schritt und in der Tag-Liste.

2. Neue Eingabe push — der Baustein taugt jetzt auch als Prüflauf

push (boolean, Vorgabe true) steuert, ob das Image veröffentlicht wird. Bei
push: false:

  • kein 1Password-Schritt — die Zugangsdaten werden gar nicht erst geholt,
  • kein docker/login-action,
  • docker/build-push-action mit push: false.

Bei push: true bleibt alles exakt wie bisher. Damit nutzt die CI eines
Projekts dieselbe Bau-Logik wie das Deployment, statt sie zu duplizieren.

⚠️ Private Submodule funktionieren im Prüfmodus nicht

Der Checkout-Token stammt aus demselben 1Password-Schritt, der ohne Push
übersprungen wird. Der Checkout fällt deshalb auf den Standard-Token des Jobs
zurück. Der genügt für das ausführende Repository, nicht aber für die Submodule
in eigenen privaten Repositories — deren Klon scheitert mit
Repository not found.

Projekte mit privaten Submodulen können push: false daher nicht nutzen.
Für Projekte ohne Submodule ist der Modus unproblematisch. Dokumentiert als
Kommentar am 1Password-Schritt und als hervorgehobener Hinweis in der README.

Die Versionsangaben sind nicht mehr Pflicht

Die vier version-number*-Eingaben dienen ausschließlich der Tag-Liste, die
ohne Push bedeutungslos ist — ein reiner Prüflauf müsste sonst Platzhalter
erfinden. required von true auf false zu senken ist rückwärtskompatibel;
wer pusht, übergibt sie unverändert weiter.

Die Vorgabe ist 0 bzw. 0.0.0 und nicht leer: docker/build-push-action
gibt die Tag-Liste auch ohne Push als --tag an den Build weiter. Ein leerer
Wert ergäbe <registry>/<owner>/<name>: und ließe den Build an
invalid reference format scheitern.

Warum die if:-Bedingung so geschrieben ist

Ein boolescher workflow_call-Input kann je nach Schreibweise des Aufrufers als
echter Boolean oder als String ankommen. Beide naheliegenden Formen sind
falsch, jede in einem anderen Fall:

inputs.push inputs.push
(naiv)
inputs.push == 'true'
(naiv)
inputs.push && inputs.push != 'false'
(gewählt)
true (bool) ✅ wahr ❌ falsch ✅ wahr
false (bool) ✅ falsch ✅ falsch ✅ falsch
'true' (str) ✅ wahr ✅ wahr ✅ wahr
'false' (str) ❌ wahr ✅ falsch ✅ falsch

inputs.push allein ist beim String 'false' wahr — nichtleere Strings sind
truthy. inputs.push == 'true' ist beim echten Boolean true falsch — ein
Vergleich über Typgrenzen läuft über Zahlen, 'true' wird dabei zu NaN. Die
gewählte Kombination deckt alle vier Fälle ab und rendert im with: der
Build-Action immer exakt "true" oder "false".

Kein stiller Fehler aus leeren Step-Outputs

Wird der 1Password-Schritt übersprungen, sind seine Outputs leer. Der Login-
Schritt ist über dieselbe Bedingung abgeschaltet, sodass kein Login mit leerem
Benutzer versucht wird. Beim Checkout hätte ein leer übergebenes token die
Vorgabe der Action (github.token) überschrieben und den Checkout ohne jeden
Zugang laufen lassen — er wäre schon am eigenen Repository gescheitert. Deshalb:

token: ${{ steps.op-secrets.outputs.CHECKOUT_TOKEN || github.token }}

3. Unverändert

Alles Übrige ist Zeile für Zeile übernommen, samt der Kommentare, die das Warum
erklären: 1Password Connect, runs-on: [ ubuntu-docker ], DOCKER_HOST aus
vars, der Diagnoseschritt für den Socket, Multi-Arch und das Tag-Schema.

README

Ergänzt um push und registry in der Eingabetabelle, die Begründung der
Asymmetrie zwischen image-owner und registry, einen eigenen Abschnitt
Prüfmodus ohne Push mit zweitem Aufrufbeispiel (ohne Secrets, ohne Versionen)
und den hervorgehobenen Hinweis zu privaten Submodulen.

Absicherung

Der Workflow hat keinen eigenen Auslöser außer workflow_call und lässt sich
hier nicht ausführen; Container-Images lassen sich in diesem Workspace ohnehin
nicht bauen. Geprüft wurde deshalb statisch:

  • YAML geparst (js-yaml, YAML-1.2-Core-Schema wie bei Gitea): Top-Level-
    Schlüssel sind nur noch on, name, jobs — on bleibt ein String-Schlüssel
    und wird nicht zu true verschluckt; einen Schlüssel true gibt es nicht.
  • env auf Workflow-Ebene ist weg, und keine env.*-Referenz zeigt mehr
    ins Leere: die einzige verbliebene env-Deklaration ist DOCKER_HOST auf
    Job-Ebene.
  • Alle 39 ${{ … }}-Ausdrücke aufgelöst: jede der 13 inputs.*-Referenzen
    zeigt auf eine deklarierte Eingabe, keine Eingabe ist ungenutzt, die
    steps.*-Referenzen zeigen auf die IDs op-secrets und buildx.
  • Typen der Vorgaben geprüft: push kommt als echter Boolean durch,
    timeout-minutes als Zahl, version-number als String "0.0.0".
  • if:-Bedingungen gegen alle vier Eingabeformen durchgerechnet (Tabelle
    oben) — beide Bedingungen sind identisch und in jedem Fall korrekt.
  • Diff gegen den vorherigen Stand belegt: außer Push-Steuerung, Eigentümer
    und Registry weicht nichts ab. Alle 29 Kommentarzeilen der Vorfassung sind
    zeichengleich erhalten, es kamen 54 erklärende hinzu.

Nach dem Merge

Damit Aufrufer auf etwas Stabiles zeigen können, braucht es einen Tag (v1);
das Aufrufbeispiel in der README nennt ihn bereits.

Fixes #1 Die bisher in einem einzelnen Projekt liegende Build-and-Push-Pipeline zieht als wiederverwendbarer Workflow nach `.gitea/workflows/docker-build-push.yml` um — und wird dabei an drei Stellen von der Herkunft aus einem einzelnen Projekt gelöst. ## 1. Das Image-Präfix `IMAGE_OWNER: suer` stand fest im `env`-Block, `REGISTRY: git.jk-dev.de` ebenfalls. Unverändert übernommen wäre jedes Image jedes aufrufenden Projekts unter `git.jk-dev.de/suer/…` gelandet. Beide sind jetzt Eingaben — mit **bewusster Asymmetrie**: | Eingabe | Pflicht | Vorgabe | | ------------- | ------- | --------------- | | `image-owner` | **ja** | *keine* | | `registry` | nein | `git.jk-dev.de` | Der Eigentümer ist je Projekt verschieden und soll nie stillschweigend geraten werden — genau das war der Fehler des fest verdrahteten `suer`. Die Registry ist dagegen praktisch immer dieselbe. > **Nicht rückwärtskompatibel:** Bestehende Aufrufer müssen `image-owner` > ergänzen, sonst schlägt der Aufruf an der fehlenden Pflichteingabe fehl. Das > ist gewollt. Der `env`-Block auf Workflow-Ebene entfällt damit ersatzlos — sein einziger verbliebener Eintrag war `REGISTRY`. Bleiben konnte der Wert dort ohnehin nicht: `env` auf Workflow-Ebene wird ausgewertet, bevor es einen `inputs`-Kontext gibt. Registry und Eigentümer werden deshalb dort gelesen, wo der Kontext sicher vorhanden ist — im Login-Schritt und in der Tag-Liste. ## 2. Neue Eingabe `push` — der Baustein taugt jetzt auch als Prüflauf `push` (boolean, Vorgabe `true`) steuert, ob das Image veröffentlicht wird. Bei `push: false`: - **kein 1Password-Schritt** — die Zugangsdaten werden gar nicht erst geholt, - **kein `docker/login-action`**, - `docker/build-push-action` mit `push: false`. Bei `push: true` bleibt alles exakt wie bisher. Damit nutzt die CI eines Projekts dieselbe Bau-Logik wie das Deployment, statt sie zu duplizieren. ### ⚠️ Private Submodule funktionieren im Prüfmodus nicht Der Checkout-Token stammt aus **demselben** 1Password-Schritt, der ohne Push übersprungen wird. Der Checkout fällt deshalb auf den Standard-Token des Jobs zurück. Der genügt für das ausführende Repository, nicht aber für die Submodule in eigenen privaten Repositories — deren Klon scheitert mit `Repository not found`. Projekte **mit** privaten Submodulen können `push: false` daher nicht nutzen. Für Projekte ohne Submodule ist der Modus unproblematisch. Dokumentiert als Kommentar am 1Password-Schritt und als hervorgehobener Hinweis in der README. ### Die Versionsangaben sind nicht mehr Pflicht Die vier `version-number*`-Eingaben dienen ausschließlich der Tag-Liste, die ohne Push bedeutungslos ist — ein reiner Prüflauf müsste sonst Platzhalter erfinden. `required` von `true` auf `false` zu senken ist rückwärtskompatibel; wer pusht, übergibt sie unverändert weiter. Die Vorgabe ist `0` bzw. `0.0.0` und **nicht leer**: `docker/build-push-action` gibt die Tag-Liste auch ohne Push als `--tag` an den Build weiter. Ein leerer Wert ergäbe `<registry>/<owner>/<name>:` und ließe den Build an `invalid reference format` scheitern. ### Warum die `if:`-Bedingung so geschrieben ist Ein boolescher `workflow_call`-Input kann je nach Schreibweise des Aufrufers als echter Boolean **oder** als String ankommen. Beide naheliegenden Formen sind falsch, jede in einem anderen Fall: | `inputs.push` | `inputs.push`<br>(naiv) | `inputs.push == 'true'`<br>(naiv) | `inputs.push && inputs.push != 'false'`<br>(gewählt) | | ------------- | ---- | ---- | ---- | | `true` (bool) | ✅ wahr | ❌ **falsch** | ✅ wahr | | `false` (bool) | ✅ falsch | ✅ falsch | ✅ falsch | | `'true'` (str) | ✅ wahr | ✅ wahr | ✅ wahr | | `'false'` (str) | ❌ **wahr** | ✅ falsch | ✅ falsch | `inputs.push` allein ist beim String `'false'` wahr — nichtleere Strings sind truthy. `inputs.push == 'true'` ist beim echten Boolean `true` falsch — ein Vergleich über Typgrenzen läuft über Zahlen, `'true'` wird dabei zu `NaN`. Die gewählte Kombination deckt alle vier Fälle ab und rendert im `with:` der Build-Action immer exakt `"true"` oder `"false"`. ### Kein stiller Fehler aus leeren Step-Outputs Wird der 1Password-Schritt übersprungen, sind seine Outputs leer. Der Login- Schritt ist über dieselbe Bedingung abgeschaltet, sodass kein Login mit leerem Benutzer versucht wird. Beim Checkout hätte ein leer übergebenes `token` die Vorgabe der Action (`github.token`) überschrieben und den Checkout ohne jeden Zugang laufen lassen — er wäre schon am eigenen Repository gescheitert. Deshalb: ```yaml token: ${{ steps.op-secrets.outputs.CHECKOUT_TOKEN || github.token }} ``` ## 3. Unverändert Alles Übrige ist Zeile für Zeile übernommen, samt der Kommentare, die das Warum erklären: 1Password Connect, `runs-on: [ ubuntu-docker ]`, `DOCKER_HOST` aus `vars`, der Diagnoseschritt für den Socket, Multi-Arch und das Tag-Schema. ## README Ergänzt um `push` und `registry` in der Eingabetabelle, die Begründung der Asymmetrie zwischen `image-owner` und `registry`, einen eigenen Abschnitt *Prüfmodus ohne Push* mit zweitem Aufrufbeispiel (ohne Secrets, ohne Versionen) und den hervorgehobenen Hinweis zu privaten Submodulen. ## Absicherung Der Workflow hat keinen eigenen Auslöser außer `workflow_call` und lässt sich hier nicht ausführen; Container-Images lassen sich in diesem Workspace ohnehin nicht bauen. Geprüft wurde deshalb statisch: - **YAML geparst** (js-yaml, YAML-1.2-Core-Schema wie bei Gitea): Top-Level- Schlüssel sind nur noch `on`, `name`, `jobs` — `on` bleibt ein String-Schlüssel und wird nicht zu `true` verschluckt; einen Schlüssel `true` gibt es nicht. - **`env` auf Workflow-Ebene ist weg**, und keine `env.*`-Referenz zeigt mehr ins Leere: die einzige verbliebene `env`-Deklaration ist `DOCKER_HOST` auf Job-Ebene. - **Alle 39 `${{ … }}`-Ausdrücke aufgelöst**: jede der 13 `inputs.*`-Referenzen zeigt auf eine deklarierte Eingabe, keine Eingabe ist ungenutzt, die `steps.*`-Referenzen zeigen auf die IDs `op-secrets` und `buildx`. - **Typen der Vorgaben geprüft**: `push` kommt als echter Boolean durch, `timeout-minutes` als Zahl, `version-number` als String `"0.0.0"`. - **`if:`-Bedingungen** gegen alle vier Eingabeformen durchgerechnet (Tabelle oben) — beide Bedingungen sind identisch und in jedem Fall korrekt. - **Diff gegen den vorherigen Stand** belegt: außer Push-Steuerung, Eigentümer und Registry weicht nichts ab. Alle 29 Kommentarzeilen der Vorfassung sind zeichengleich erhalten, es kamen 54 erklärende hinzu. ## Nach dem Merge Damit Aufrufer auf etwas Stabiles zeigen können, braucht es einen Tag (`v1`); das Aufrufbeispiel in der README nennt ihn bereits.
bk-dev-bot added 1 commit 2026-09-30 14:17:02 +02:00
Die bisher in einem einzelnen Projekt liegende Pipeline zieht als
wiederverwendbarer Workflow (on: workflow_call) nach
.gitea/workflows/docker-build-push.yml um.

Inhaltlich geaendert ist genau eine Stelle: Der Eigentuemer des Images
stand als IMAGE_OWNER fest im env-Block des Workflows, wodurch jedes
Image jedes aufrufenden Projekts unter git.jk-dev.de/suer/... gelandet
waere. Er ist jetzt die Eingabe image-owner mit suer als Vorgabewert,
sodass bestehende Aufrufer unveraendert weiterlaufen.

Der Wert kann nicht im env-Block bleiben: env auf Workflow-Ebene wird
ausgewertet, bevor es einen inputs-Kontext gibt. Die Tag-Liste liest ihn
deshalb unmittelbar aus inputs.

Alles Uebrige ist unveraendert uebernommen, samt der Kommentare, die das
Warum erklaeren: 1Password Connect, runs-on, DOCKER_HOST, Diagnoseschritt,
Multi-Arch und Tag-Schema.

Die README dokumentiert Eingaben, ein vollstaendiges Aufrufbeispiel, die
Voraussetzungen im aufrufenden Repository und den Hinweis, auf einen Tag
statt auf @main zu zeigen.
jklein requested changes 2026-09-30 15:57:04 +02:00
Dismissed
@@ -0,0 +28,4 @@
<registry>/<owner>/<container-name>. Used to be hardcoded to "suer",
which sent every image of every calling project to the same account.
Kept as the default so existing callers stay unchanged.
required: false
Owner

ist required, kein default

ist required, kein default
jklein marked this conversation as resolved
@@ -0,0 +55,4 @@
name: build and push Docker Image
env:
REGISTRY: git.jk-dev.de
Owner

soll konfigurierbar sein mit default auf git.jk-dev.de

soll konfigurierbar sein mit default auf git.jk-dev.de
jklein marked this conversation as resolved
bk-dev-bot added 1 commit 2026-09-30 16:01:23 +02:00
Der Baustein taugt jetzt auch fuer reine Prueflaeufe. Die neue Eingabe push
(boolean, Vorgabe true) steuert, ob gepusht wird. Bei push: false entfaellt der
1Password-Schritt, es findet kein Registry-Login statt und
docker/build-push-action baut mit push: false. Bei push: true bleibt alles wie
bisher.

Damit nutzt die CI eines Projekts dieselbe Bau-Logik wie das Deployment, statt
sie zu duplizieren.

Folge, die man kennen muss: der Checkout-Token stammt aus demselben
1Password-Schritt. Ohne Push faellt der Checkout auf den Standard-Token des Jobs
zurueck, und der kann keine privaten Submodule klonen. Der Modus taugt deshalb
nur fuer Projekte ohne private Submodule. Dokumentiert im Workflow und in der
README.

Die vier Versionsangaben sind nicht mehr Pflicht -- sie dienen ausschliesslich
der Tag-Liste, die ohne Push bedeutungslos ist, und ein Prueflauf muesste sonst
Platzhalter erfinden. required von true auf false zu senken ist
rueckwaertskompatibel. Die Vorgabe ist 0 bzw. 0.0.0 und nicht leer: die
Tag-Liste wird auch ohne Push gebildet und ein leerer Wert ergaebe das
ungueltige ".../<name>:".

Aus dem Review:

- image-owner ist jetzt required ohne Vorgabe. Der Eigentuemer ist je Projekt
  verschieden und soll nie stillschweigend geraten werden -- genau das war der
  Fehler des frueheren, fest verdrahteten "suer". Bestehende Aufrufer muessen
  ihn dadurch ergaenzen; das ist gewollt.
- REGISTRY ist von einem festen env-Eintrag zur Eingabe registry mit Vorgabe
  git.jk-dev.de geworden. Der env-Block auf Workflow-Ebene entfaellt damit
  ersatzlos -- er kannte ohnehin keinen inputs-Kontext. Registry und
  Eigentuemer werden dort gelesen, wo der Kontext sicher vorhanden ist: im
  Login-Schritt und in der Tag-Liste.
jklein approved these changes 2026-09-30 16:10:59 +02:00
jklein merged commit 47b77fac54 into main 2026-09-30 16:11:05 +02:00
jklein deleted branch docker-build-push 2026-09-30 16:11:06 +02:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: workflow-util/pipelines#2