Docker multiplatform Build mit Gitea
Docker multiplatform Builds
Wenn man, so wie ich, daheim einen kleinen Kubernetes Cluster betreibt (raspberry und on demand ein paar Intel Nodes) dann kommt man irgendwann zu dem Punkt nicht jedes Experiment auf docker-hub pushen zu wollen.
Also hab ich mir ein privates Docker-Repository aufgesetzt. Das ganze dann noch mit einem DNS-Namen versehen (nicht unbedingt notwendig) und fertig …. naja … nicht ganz. Ist aber eine andere, ebenfalls längere, Geschichte.
Gitea und Gitea Runner
Damit man den eigenen Source-Code auch halbwegs verwalten kann, eignet sich für daheim das Projekt Gitea. Dafür gibts ebenfalls docker container - es empfiehlt sich aber, das Data-Verzeichnis irgendwo persistent zu halten, damit man nicht bei jedem Update und Restart sein gesamtes Lebenswerk verliert.
Die eigene CI
Gitea erlaubt es, sogenannte Runner anzudocken, die dann Tasks, eigentlich GitHub Actions, ausführen. Ich hab dafür einen Raspberry 4 verwendet und einfach aus dem Internet ein kleines systemd-script verwendet, das den Runner beim Boot startet.
Bitte aufpassen, dass ihr für die Node “ubuntu-latest” oder ähnliches einstellt, damit euch bei den Actions auch der Runner zugewiesen wird (runs-on: ubuntu-latest
falls ihr das schon mal gesehen habt).
Docker build
Hier hab ich die fertigen GitHub actions verwendet:
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: myregistry.private.local/app:latest
Das funktioniert schon mal nicht, da docker sich weigert, auf das private registry zu pushen. Kein HTTPS, und wenn doch, dann ist das Zertifikat nicht vertrauenswürdig.
Konfiguration für das private Repo
Für mein Repo hab ich einen nginx als proxy. damit kann man auch gleich HTTPS. Mit etwas hilfe von Google schafft man auch mittels oppenssl
ein eigenes Zertifikat zu erstellen und dem proxy beizubringen.
Vertrauenswürdig ist das aber leider noch immer nicht.
Für Multi-Platform Builds wird bei Docker fast immer Buildkit verwendet (docker buildx
). Das nimmt auch gerne eine Konfiguration mit der man Docker beibringen kann mit https und Zertifikaten nicht so pingelig zu sein.
Buildkitd.toml
debug = true
insecure-entitlements = [ "network.host", "security.insecure" ]
[registry."myregistry.private.local"]
insecure = true
http = true
ca = [".gitea/cert.crt"]
Das Beispiel zeigt mein eigenes zertifikat. Zusätzlich hab ich ihm auch noch erlaubt http
zu benutzen und auf Sicherheit zu verzichten. Es geht sicher auch mit weniger aufwand, aber bei mir hat das nach langer Zeit genau so funktioniert.
Erster Versuch - Github action
Gleich mal das YAML anpassen:
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
buildkitd-config: buildkitd.toml
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
platforms:
- linux/arm64
- linux/amd64
tags: myregistry.private.local/app:latest
Das funktioniert auch, braucht aber ewig. Grund dafür ist, dass jede nicht native Platform über QEMU emuliert wird. Da ich aber neben dem Raspberry auch einen AMD64 Docker host hab, wollt ich beides ganz normal bauen. Grundsätzlich funktioniert das ja auch.
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
buildkitd-config: buildkitd.toml
append: |
- endpoint: tcp://192.168.10.1:2375
platforms: linux/amd64
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
platforms:
- linux/arm64
- linux/amd64
tags: myregistry.private.local/app:latest
Ja … und dann brauchte ich mehrere Wochen um zu der Einsicht zu gelangen, dass die ‘setup-buildx-action’ das nicht unterstützt, weil die buildkitd-config nur für die Haupt-Node verwendet wird. Es gibt keine Möglichkeit, diese auch an die anderen Nodes weiterzugeben. 🤦♂️🤦♂️🤦♂️
Auflösung des Rätsels
Leider muss man das nach wie vor mit Command-Line-Tools machen …..
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
with:
submodules: true
- name: Prepare docker context
run: |
docker buildx create --name build_context --driver docker-container --bootstrap --buildkitd-config ./buildkitd.toml
docker context create arm_node --docker "host=tcp://192.168.5.3:2375"
docker buildx create --name build_context --append arm_node --bootstrap --buildkitd-config ./buildkitd.toml
docker context create amd_node --docker "host=tcp://192.168.5.4:2375"
docker buildx create --name build_context --append amd_node --bootstrap --buildkitd-config ./buildkitd.toml
- name: Build Docker
run: |
docker buildx build --platform linux/amd64,linux/arm64 -t myregistry.private.local/app:latest -t myregistry.private.local/app:1 --builder build_context -o type=registry --push .
- name: Clean up
if: always()
run: |
docker buildx rm build_context
docker context rm arm_node
docker context rm amd_node
das if: always()
sorgt übrigens dafür dass der Context aufgeräumt wird, auch wenn der Build fehlschlägt.
Lesson learned
Vertraue nie darauf, dass ein anderes Softwareprodukt mehr macht als direkt in der Dokumentation steht. Auch nicht, wenn es auf der Hand liegen würde oder logisch erscheint.