docker build && docker push vom eigenen Rechner aus funktioniert, solange niemand sonst dasselbe Image veröffentlichen muss oder es nicht bei jedem Merge passieren muss, ohne dass Sie an der Tastatur sitzen. GitHub Actions führt diesen Build auf den Runnern von GitHub aus, taggt das Image passend zu dem, was den Workflow ausgelöst hat, und pusht es zu einer Registry, aus der Ihr Deploy-Schritt pullen kann. Nichts läuft lokal.
Was es braucht, um ein Docker-Image aus der CI heraus zu bauen und zu pushen
FĂĽnf Teile mĂĽssen vorhanden sein, bevor ein Runner ein Image pushen kann:
actions/checkoutfĂĽr den Build-Kontext: Ihr Dockerfile und Ihr Quellcodedocker/setup-buildx-actionfĂĽr einen BuildKit-Builder, weil die vorinstallierte Docker Engine des Runners weder Layer-Cache exportieren noch fĂĽr andere Plattformen bauen kanndocker/login-actionfĂĽr die Registry-Zugangsdatendocker/metadata-action, um aus dem Git-Ref eine Tag-Liste und OCI-Labels zu machendocker/build-push-action, um Build und Push in einem einzigen Aufruf auszufĂĽhren
Docker pflegt alle fĂĽnf, und sie sind so geschrieben, dass sie zusammenpassen: Die Ausgaben des Metadata-Schritts flieĂźen direkt in die Eingaben des Build-Schritts. Darunter steckt derselbe docker build und docker push, den Sie von Hand ausfĂĽhren, nur auf einen Runner verlagert und an Git-Events gekoppelt.
Warum der Runner Buildx braucht, bevor er ĂĽberhaupt etwas bauen kann
ubuntu-latest bringt Docker Engine mit, die Images mit docker build ohne Probleme baut. Was sie allein nicht kann: einen Build-Cache irgendwo außer auf der lokalen Festplatte ablegen oder für eine andere Plattform als die des Runners bauen. docker/setup-buildx-action erstellt einen Buildx-Builder auf Basis von BuildKit und setzt ihn als Standard, was cache-to/cache-from und Multi-Plattform-Builds später in diesem Beitrag erst möglich macht.
| |
Nur ein Schritt, im üblichen Fall ohne benötigte Eingaben. Lassen Sie ihn weg, läuft build-push-action trotzdem, mit dem Builder, den der Runner schon hat — der Build gelingt, aber Cache-Export und Multi-Arch fallen weg.
Wie man sich mit dem eingebauten Token bei GHCR anmeldet
Jeder Workflow-Lauf bekommt einen kurzlebigen GITHUB_TOKEN, dessen Scope auf das Repository beschränkt ist, in dem er läuft. Um in die GitHub Container Registry (ghcr.io) zu pushen, reicht dieser Token: kein Personal Access Token, kein Secret, das erstellt oder rotiert werden müsste.
| |
Der permissions-Block zählt genauso viel wie der Login-Schritt. Ohne packages: write kann der Token, den GitHub dem Workflow gibt, zwar lesen, aber nicht pushen — der Login gelingt, der Push scheitert mit einem 403. In einem Organisations-Repository gibt es eine zweite Stelle, die man prüfen sollte: Eine Workflow-Berechtigungseinstellung unter Settings → Actions → General kann Tokens unabhängig davon, was die Workflow-Datei verlangt, auf Nur-Lesen begrenzen. Wenn der Push mit dem obigen Block trotzdem scheitert, liegt es oft daran.
Wie man Images automatisch anhand des Git-Refs taggt
Einen Tag fest in die Workflow-Datei zu schreiben bedeutet, bei jeder Release das YAML zu ändern. docker/metadata-action liest das Event, das den Lauf ausgelöst hat (welcher Branch, welcher Tag, welche PR), und erzeugt die Tag-Liste und die OCI-Labels für Sie.
| |
Sie pushen den Tag v1.4.0, und die semver-Regel erzeugt 1.4.0. Sie pushen zu main, und die raw-Regel fügt latest hinzu. Jeder Build bekommt unabhängig vom Auslöser einen Tag mit der Short-SHA, sodass ein bestimmter Commit auch dann noch abrufbar bleibt, wenn latest längst weitergezogen ist. steps.meta.outputs.tags ist eine durch Zeilenumbrüche getrennte Liste der Tags, die diese drei Regeln für dieses eine Event erzeugen — nicht die Vereinigung aller drei über alle Läufe hinweg.
Wie der vollständige Workflow aussieht
Fügen Sie die Teile zusammen und ergänzen Sie docker/build-push-action, das docker build und docker push in einem einzigen BuildKit-Aufruf erledigt:
| |
context: . baut aus dem gerade ausgecheckten Repository mit dem Dockerfile, das dort liegt; zeigen Sie mit file: auf einen anderen Pfad, wenn Ihres woanders liegt. Was am Ende im Image landet, entscheidet weiterhin das Dockerfile — von der Reihenfolge der Layer bis zu einem nicht-root USER. Dieser Workflow legt nur fest, wann der Build läuft und wohin das Ergebnis geht.
Eine Sache sollten Sie gleich richtig machen: Braucht der Build selbst Zugangsdaten – etwa einen Token für eine private npm-Registry –, geben Sie ihn über die secrets:-Eingabe der Action weiter statt über ein build-arg, das in der Image-Historie sichtbar bleibt. Umgebungsvariablen und Secrets in Docker geht auf diesen Unterschied ein.
Wie man Layer zwischen Workflow-Läufen cacht
Ein von GitHub gehosteter Runner startet jedes Mal sauber. Ohne externen Cache wird bei jedem Lauf jeder Layer neu gebaut, RUN npm ci eingeschlossen. cache-from: type=gha und cache-to: type=gha,mode=max im obigen Schritt weisen BuildKit an, seinen Cache ĂĽber den Cache-Dienst von GitHub Actions statt ĂĽber die lokale Festplatte zu lesen und zu schreiben, sodass der Cache den Runner ĂĽberlebt.
mode=max cacht jeden Zwischenlayer, auch die aus früheren Stages eines Multi-Stage-Dockerfiles. Der Standard, mode=min, behält nur die Layer, die im finalen Image landen — die Build-Stage eines Multi-Stage-Dockerfiles wird dann bei jedem Lauf komplett neu kompiliert.
Der Actions-Cache hat eine Größenobergrenze pro Repository und entfernt alte Einträge nach LRU-Logik. Bei den meisten Anwendungs-Images kommt das nie zum Tragen. Ein Repository, das mehrere große Multi-Arch-Images baut, kann daran stoßen, und ein registry-gestützter Cache (cache-to: type=registry,ref=ghcr.io/org/app:buildcache) ist der Ausweg, da er nicht durch das Actions-Cache-Kontingent gedeckelt ist.
Wie man bei jedem Push baut, aber nur bei einer Release pusht
Der obige Workflow pusht bei jedem Push zu main und jedem Versions-Tag, was für ein Projekt, das von main aus deployt, der richtige Standard ist. Eine vorsichtigere Variante baut und testet jede Pull Request, ohne etwas zu veröffentlichen, und pusht erst nach einem Merge:
| |
Der vollständige Build läuft trotzdem auf der PR, sodass ein kaputtes Dockerfile vor dem Merge scheitert, während push: false das Schreiben in die Registry überspringt. Ist cache-to: type=gha schon vorhanden, bereitet dieser PR-Build auch den Cache vor, den der Push nach dem Merge wiederverwendet.
Was sich ändert, wenn Sie zu Docker Hub statt GHCR pushen
Docker Hub braucht ein anderes Registry-Argument und andere Zugangsdaten: einen Access Token aus Account Settings → Security, nicht das Passwort Ihres Kontos, das Docker Hub für API-Logins nicht mehr akzeptiert.
| |
Buildx-Setup, Metadata-Extraktion und der Build-Push-Schritt ändern sich nicht. Die Registry ist ein Parameter, keine andere Pipeline.
Was dieser Workflow nicht abdeckt
Multi-Plattform-Images (linux/amd64 plus linux/arm64 in einem Manifest) brauchen docker/setup-qemu-action vor Buildx und eine platforms:-Eingabe im Build-Schritt. Die QEMU-Emulation macht einen arm64-Build auf einem amd64-Runner langsam, oft mehrfach langsamer als nativ. Wer regelmäßig für arm64 baut, entfernt mit den arm64-gehosteten Runnern von GitHub die Emulation komplett.
Self-hosted Runner, die bereits Buildx konfiguriert haben, brauchen setup-buildx-action nicht, und ein dort dauerhaft laufender Builder kann das Actions-Cache-Backend schlagen, indem er die Layer zwischen den Läufen auf lokaler Festplatte hält. Ein Monorepo, das drei Service-Images veröffentlicht, wechselt meist zu einer Build-matrix statt zu drei Kopien dieses Jobs — das ändert, wie context und images parametrisiert werden, aber nicht die Schritte selbst.
Den Workflow testen, bevor Sie eine Release taggen
Pushen Sie zuerst auf einen Branch, nicht auf einen Versions-Tag. Schauen Sie sich den Lauf im Actions-Tab an, prüfen Sie dann das Image auf der Packages-Seite Ihres Repositorys, oder pullen Sie es direkt mit docker pull ghcr.io/<owner>/<repo>:<short-sha> mit dem Tag aus dem Lauf-Log. Sobald dieser Pull funktioniert, ist eine Release derselbe git tag v1.0.0 && git push --tags, den Sie schon verwenden — den Rest übernimmt der Workflow.