CodeGym /Kurse /Docker for Spring /amd64 und arm64 ohne Verwirrung

amd64 und arm64 ohne Verwirrung

Docker for Spring
Level 7, Lektion 2
Verfügbar

1. Plattform in Docker: Kontext und Begriffe

Nach dem Multi-Stage und der Diskussion über tag/digest tappt man leicht in eine Falle: Es scheint, dass durch das „Fixieren“ eines Images alles völlig vorhersagbar wird. In der Praxis entsteht jedoch schnell eine gemischte Realität im Team: jemand arbeitet mit Windows, jemand mit Linux, jemand mit macOS auf Apple Silicon. Und plötzlich stellt sich heraus, dass derselbe Image-Name unterschiedliche Binärdateien für verschiedene CPUs bedeuten kann.

Es ist wichtig, den richtigen Ton zu treffen: Es geht nicht um ein „komplexes DevOps-Thema“, sondern um einen normalen Teil des Ingenieurdenkens über die Umgebung. Wir werden nicht in Multi-Platform-Veröffentlichungen oder komplexe Pipelines abtauchen. Wir brauchen genau so viel, um zu verstehen, warum bei einem Studenten auf Mac M1 der Build anders läuft und wie man platform mismatch nicht mit „Spring ist kaputt“ verwechselt.

Beginnen wir mit der kürzesten Problembeschreibung: Ihr jar ist fast plattformunabhängig, aber die JVM, das Linux-Userland und native Tools im Base-Image sind plattformabhängig. Daher kann sich derselbe Dockerfile auf amd64 und arm64 unterschiedlich verhalten.

Platform: Betriebssystem und Architektur

Wenn Docker von platform spricht, meint es meist die Kombination aus Betriebssystem und CPU-Architektur im Format os/arch. Im Rahmen dieses Kurses sprechen wir fast immer von Linux-Containern, daher werdet ihr meist linux/amd64 oder linux/arm64 sehen. Dies ist wie ein Pass des Images: „Für welches Betriebssystem und welchen Prozessor sind die Binärdateien im Inneren kompiliert“.

Die Verwirrung bei Anfängern beginnt mit einer einfachen menschlichen Logik: „Aber ich habe doch macOS! Warum steht hier Linux?“ Antwort: Weil Docker Desktop normalerweise Linux-Container innerhalb einer Linux-Umgebung (in einer VM) ausführt, selbst wenn das Host-Betriebssystem macOS oder Windows ist. Container in unserem Kurs sind Linux-Container, und das ist grundlegend wichtig.

Um die Begriffe zu festigen, hier eine kleine Tabelle – ohne „Enzyklopädie“, nur das, was im Leben eines Java-Entwicklers wirklich vorkommt:

Was ihr seht Wie zu lesen Typische Situation
linux/amd64 Linux + x86_64 (meistens „normale“ Intel/AMD) die meisten CIs, viele Linux/Windows-Maschinen, viele Server
linux/arm64 Linux + ARM 64-bit Apple Silicon (M1/M2/M3), einige Dev-Maschinen, einige Server
os.arch=amd64 JVM sieht die Architektur x86_64 Java wird innerhalb einer amd64-Umgebung ausgeführt
os.arch=aarch64 JVM sieht ARM64 Java wird innerhalb einer arm64-Umgebung ausgeführt

Ein wichtiger kleiner Hinweis, damit keine Magie aufkommt: In Java wird die Architektur oft als aarch64 bezeichnet, in Docker als arm64. Es sind nicht zwei verschiedene Welten, sondern zwei unterschiedliche Benennungstraditionen.

2. Multi-Arch-Images: Ein FROM, verschiedene Varianten

Jetzt kommt der Moment, in dem es hilfreich ist, sich vorzustellen, dass ein einzelnes „Image“ in der Docker-Registry nicht immer eine einzige Datei ist. Oft ist es eine Sammlung von Varianten für verschiedene Plattformen (Multi-Arch). Ihr schreibt in Dockerfile etwas wie FROM some-java-runtime:25, und Docker wählt beim Build/Pull automatisch die passende Variante für eure Plattform aus.

Und hier kommt eine wichtige Verbindung zu tag/digest ins Spiel. Ein tag ist für Menschen praktisch, aber er kann auf eine Menge von Varianten verweisen. Ein digest hingegen fixiert einen unveränderlichen Referenzpunkt und schützt vor Drift über die Zeit. Das reicht jedoch immer noch nicht aus, um automatisch denselben plattformspezifischen Binär-Layer auf amd64 und arm64 zu erhalten: In einem Multi-Arch-Image leben verschiedene Varianten, und welche ausgewählt wird, hängt zusätzlich von der Plattform ab. Reproducibility von Referenzen und Kontrolle der Plattform sind daher benachbarte, aber unterschiedliche Aufgaben.

Es ist hilfreich, dies als einfaches Blockdiagramm zu sehen:

flowchart TD
    %% Wie Docker die Image-Variante für Ihre Plattform auswählt
    A["Dockerfile: FROM some-java-runtime:25"] --> B{"Gibt es eine Variante für Ihre Plattform?"}
    B -->|Ja| C["Docker wählt linux/amd64 oder linux/arm64"]
    B -->|Nein| D["Fehler: no matching manifest ..."]
    C --> E["Build und Ausführung gehen weiter"]

Der Kernpunkt: Wenn das Image keine Variante für eure Architektur hat, bekommt ihr ein Problem noch bevor die Anwendung startet. Und das wird aussehen wie „Docker ist kaputt“ oder „irgendein Unsinn mit dem Basis-Image“, obwohl es in Wirklichkeit einfach das Fehlen der benötigten Plattform ist.

3. Host, Docker Desktop und Linux-Images

Wenn ihr auf macOS oder Windows arbeitet und Linux-Container ausführt, lebt ihr gleichzeitig in drei Realitätsebenen. Das ist normal, verursacht aber bei Anfängern einen „kognitiven Bug“: Es scheint, als sei ein Container ein separater Computer und Dockerfile ein magisches Skript, das nichts mit eurem Rechner zu tun hat. Tatsächlich ist die Verbindung sehr direkt: Die Plattform eures Rechners beeinflusst, welche Image-Variante standardmäßig ausgewählt wird.

Auf Modellebene ist es nützlich, sich dieses Bild zu merken:

flowchart LR
    %% Wichtiger Gedanke: Selbst auf macOS/Windows arbeitet ihr normalerweise mit Linux-Containern (Architektur ändert sich)
    Host["Euer Laptop macOS / Windows / Linux"] --> Docker["Docker Engine / Docker Desktop"]
    Docker --> VM["Linux-Umgebung (oft VM)"]
    VM --> C1["Container: linux/arm64 oder linux/amd64"]

Und jetzt eine wichtige praktische Schlussfolgerung für den Kurs: Selbst wenn ihr macOS habt, baut und startet ihr meistens Linux-Images, und die Plattform wird als linux/* erscheinen. Unterschiedlich wird nur die Architektur sein: amd64 oder arm64.

Um die Architektur als beobachtbare Sache zu erfühlen, könnt ihr einen winzigen Java-Code nehmen. Er hängt nicht von Spring, Docker oder eurem Projekt ab, er zeigt einfach, was die JVM sieht:

public class PlatformInfo {
    public static void main(String[] args) {
        // Name des Betriebssystems "innerhalb" der Umgebung, in der die JVM läuft (im Container fast immer Linux)
        System.out.println("os.name = " + System.getProperty("os.name")); // z.B.: os.name = Linux

        // Architektur der JVM/Umgebung: Für ARM64 oft aarch64, für x86_64 - amd64
        System.out.println("os.arch = " + System.getProperty("os.arch")); // z.B.: os.arch = aarch64
    }
}

Wenn dieser Code innerhalb eines Containers ausgeführt wird, ist os.name fast sicher Linux, selbst wenn euer Laptop macOS ist. Hingegen zeigt os.arch die Architektur der Container-Umgebung: aarch64 oder amd64.

4. Wo tauchen amd64 und arm64 in Java/Spring auf

Das Thema Architektur kommt meist nicht zu euch, weil ihr gerne leidet, sondern weil die moderne Welt es unvermeidlich macht. In einer Lerngruppe gibt es fast immer jemanden mit Apple Silicon. Im Unternehmen gibt es fast immer ein CI auf amd64. Und manchmal „baut ein Entwickler ein Image bei sich“, ein anderer Entwickler „kann dasselbe Image nicht starten“, und es beginnt ein altes Ritual: „Cache leeren“, „Docker neu starten“, „alles neu installieren“, „ein Gebet zum Container-Gott sprechen“.

Lasst uns auf unseren Kurs und unser Projekt Container-Ready Catalog Service zurückkommen. Wir haben einen Multi-Stage Dockerfile etwa dieser Art:

# Stage 1: Build der Anwendung (JDK wird zum Kompilieren/Builden benötigt)
FROM some-java-jdk:25 AS builder
WORKDIR /src

# Kopieren der Quellen in den Build-Container
COPY . .

# Bauen des fat-jar / bootJar (abhängig vom Projekt)
RUN ./gradlew bootJar

# Stage 2: Runtime-Image (normalerweise leichter als JDK)
FROM some-java-runtime:25
WORKDIR /app

# Übernehmen des gebauten jar aus dem builder-Stage
COPY --from=builder /src/build/libs/app.jar app.jar

# Starten der Anwendung
ENTRYPOINT ["java", "-jar", "app.jar"]

In beiden FROM-Anweisungen stecken native Binärdateien: JDK/JRE, libc, Shell, eventuell einige Werkzeuge (je nach Distribution). Wenn ihr arm64 habt, das Image aber nur für amd64 verfügbar ist, sagt Docker entweder „kann nicht“, versucht es über Emulation oder gibt bereits beim Start des Containers einen Fehler aus.

In der Praxis könnt ihr, noch bevor ihr über Spring nachdenkt, auf einen von drei „Klassen“ von Symptomen stoßen.

5. Symptome eines platform mismatch

Wenn die Plattform nicht übereinstimmt, gibt Docker oft ziemlich ehrliche Meldungen. Das Problem ist, dass diese Meldungen „nicht anfängertauglich“ aussehen: viele Wörter, viele Zahlen, und man möchte so tun, als ginge es einen nichts an. Aber das ist genau der Fall, in dem es besser ist, den Hinweis zu lesen, als mit ihm zu streiten.

Das erste häufige Symptom ist ein Fehler beim pull oder in der FROM-Phase: Docker kann keine Image-Variante für die benötigte Architektur finden. Dann seht ihr eine Meldung wie „no matching manifest for linux/arm64…“. Das bedeutet eine sehr einfache Sache: In der Registry gibt es kein Image für eure Plattform.

Das zweite Symptom ist eine Warnung beim Start: Docker sagt, dass das Image linux/amd64 ist, euer Rechner aber linux/arm64 hat, und es startet nicht das, was erwartet wird. Oft ist das noch kein Fehler, aber es ist eine rote Flagge: entweder wird emuliert, oder irgendwann wird es wehtun.

Das dritte Symptom: Der Container startet und stürzt sofort mit etwas wie exec format error ab. Das sieht aus wie „der Startbefehl ist kaputt“, aber in Wirklichkeit ist es ein typischer Fehler, wenn eine Binärdatei für eine andere Architektur kompiliert wurde und das Betriebssystem im Container sie einfach nicht ausführen kann.

Und hier ist es methodisch wichtig: Wenn ihr solche Symptome seht, solltet ihr nicht sofort in der Spring-Konfiguration graben und application.yml reparieren. Überprüft zuerst, ob ihr das Image überhaupt für die richtige Plattform ausführt. Das ist, als wollte man den Sicherheitsgurt reparieren, während das Auto keine Räder hat: Der Gurt hat damit nichts zu tun, erscheint aber wichtig.

6. Plattformsteuerung: --platform

Wenn ihr verstanden habt, dass die Plattform ein realer Build-Parameter ist, stellt sich ganz natürlich die Frage: „Kann ich Docker direkt sagen, dass ich linux/amd64 oder linux/arm64 möchte?“ Ja, das geht. Dafür gibt es den --platform-Flag. Aber man sollte ihn wie ein Skalpell verwenden, nicht wie einen Hammer: Ein Skalpell hat einen Sinn, aber wenn man damit Nägel einschlägt, wird es seltsam und möglicherweise blutig.

Die anschaulichste Methode ist, die Plattform direkt im FROM anzugeben. Damit sagt ihr: „Ich möchte, dass dieser Stage genau diese Plattform hat.“ Zum Beispiel:

# Explizites Fixieren der Plattform des Basis-Images (nützlich für Diagnose und Reproduktion von CI)
FROM --platform=linux/amd64 some-java-runtime:25
WORKDIR /app

# Kopieren des jar (im Beispiel – bereits fertiges Artefakt)
COPY app.jar app.jar

# Starten der Anwendung
ENTRYPOINT ["java", "-jar", "app.jar"]

Das kann als Diagnoseschritt nützlich sein, um das CI-Verhalten auf amd64 zu reproduzieren, selbst wenn ihr lokal auf arm64 seid. Aber es ist wichtig zu bedenken: Wenn euer Rechner arm64 ist, kann der Build/Start von amd64 über Emulation laufen, und das wird langsamer sein. Das heißt, --platform kann das Problem „Ich möchte dasselbe bauen“ lösen, fügt aber den Preis „Es wird nicht so schnell sein“ hinzu.

Eine etwas „operativere“ Variante ist das Setzen der Plattform auf der Build-Befehlsebene. Im modernen Docker geschieht das oft über buildx:

# Explizites Bauen des Images für eine bestimmte Plattform (z. B. um den ARM-Build auf CI oder lokal zu testen)
docker buildx build --platform=linux/arm64 -t catalog-service .

Der Sinn ist einfach: Ihr baut das Image als linux/arm64. Im echten Leben hilft das, wenn ihr überprüfen wollt, ob euer Projekt auch unter ARM läuft (oder umgekehrt – die amd64-Umgebung zu reproduzieren). Wir gehen hier noch nicht in die Multi-Platform-Veröffentlichung, das heißt, wir machen nicht „einen Befehl, der sofort sowohl amd64 als auch arm64 baut und veröffentlicht“. Jetzt ist es wichtig, den Auswahlmechanismus selbst zu verstehen.

Es gibt auch docker run --platform=..., das es ermöglicht, ein Image explizit unter einer gewählten Plattform zu starten, wenn möglich. Auf Lernebene ist das nützlich, um zu unterscheiden zwischen „Image der falschen Plattform“ und „Image allgemein defekt“ oder „Anwendung startet wirklich nicht“.

7. Schnelle Überprüfung: Umgebung, Image, Container

Die Plattform ist ein Fall, in dem es besser ist, einmal auf die Fakten zu schauen, als fünfmal mit dem Gefühl zu diskutieren. Die gute Nachricht: Die Überprüfung erfolgt mit kurzen Befehlen und einem kleinen Stück Java-Code, ohne komplexe Magie.

Zunächst könnt ihr prüfen, was Docker als Architektur eurer Umgebung ansieht. Oft ist das in docker info zu sehen (die Ausgabe variiert je nach Version). Für Images und Container gibt es inspect. Um beispielsweise die Architektur eines Images zu sehen, könnt ihr eine formatierte Ausgabe verwenden:

# Zeigt OS/Architektur, für die das gegebene Image „gebaut“ ist
docker image inspect catalog-service \
  --format '{{.Os}}/{{.Architecture}}'
# Beispielausgabe: linux/arm64

Analog könnt ihr einen Container überprüfen:

# Zeigt die Plattform des konkreten Containers (wichtig, wenn ihr mit --platform startet)
docker container inspect catalog-service-container \
  --format '{{.Platform}}'
# Beispielausgabe (kann von der Version abhängen): linux/arm64

Wenn euch die Java-Ebene näher liegt, könnt ihr innerhalb des Containers (oder lokal) die JVM-Architektur ausgeben, wie wir es oben gemacht haben. Das ist besonders nützlich, wenn ihr herausfinden wollt: „Habe ich im Container wirklich ARM oder bin ich gerade zufällig in einer Emulation?“ In diesem Fall gibt os.arch ein klares Signal:

public class ArchOnly {
    public static void main(String[] args) {
        // os.arch – schnelle Möglichkeit zu verstehen, für welche Architektur die JVM tatsächlich läuft
        String arch = System.getProperty("os.arch");

        // aarch64 entspricht normalerweise linux/arm64, amd64 – linux/amd64
        System.out.println("Detected arch = " + arch); // z.B.: Detected arch = aarch64
    }
}

Der Sinn all dieser Überprüfungen ist nicht, Experte für inspect-JSON zu werden, sondern nicht länger Symptome auf der falschen Ebene zu behandeln. Wenn ihr seht, dass das Image linux/amd64 ist, die Umgebung aber linux/arm64, wird die weitere Diagnose gelassener: Ihr wisst bereits, dass dies kein „Zufall“ ist, sondern ein vollkommen erklärlicher Konflikt.

8. Typische Fehler beim Arbeiten mit den Plattformen amd64 und arm64

Fehler Nr. 1: Host-OS und Image-Plattform verwechseln.
Das klingt meist so: „Aber ich habe Windows, warum ist der Container Linux?“ oder „Ich habe macOS, also ist der Container macOS.“ In unserem Kurs arbeiten wir fast immer mit Linux-Containern, und das ist auch unter Windows/macOS normal. Die Plattform in Docker ist linux/…, unterschiedlich ist die Architektur.

Fehler Nr. 2: Docker-Warnungen ignorieren und gleich Spring reparieren.
Ein sehr häufiges Szenario: Der Container startet nicht, der Student öffnet application.yml, beginnt Port, Profile, Logging zu ändern – und bemerkt erst später, dass Docker bereits beim Start über einen Plattformkonflikt informiert hat. Wenn ihr Meldungen wie no matching manifest oder platform does not match seht, solltet ihr fast immer zuerst das klären, bevor ihr den Anwendungscode anfasst.

Fehler Nr. 3: --platform „nur zur Sicherheit“ überall hinzufügen.
Der Flag --platform ist nützlich, aber wenn ihr ihn überall und immer setzt, bekommt ihr eine neue Art von Problemen: unerwartete Emulation, langsame Builds und seltsame Verhaltensunterschiede. Eine gute Strategie auf Teamebene ist entweder, auf der nativen Plattform zu leben (arm64 auf ARM, amd64 auf x86_64), oder --platform explizit als Diagnose-/Reproduktionswerkzeug zu verwenden, nicht als dauerhafte Krücke.

Fehler Nr. 4: Alles auf einmal ändern: Vendor, tag/digest und Plattform.
Wenn ihr gleichzeitig das Basis-Image, die Tag-Version und auch noch die Plattform ändert, erhaltet ihr ein „Kombo-Chaos“: Es ist unklar, was genau das Ergebnis beeinflusst hat. Der Ingenieursansatz ist hier langweilig, aber funktioniert: Einen Parameter ändern, prüfen, Beobachtung notieren, weitermachen.

Fehler Nr. 5: Erwarten, dass „weil jar plattformunabhängig ist, das Image es auch ist“.
jar ist tatsächlich zwischen Architekturen übertragbar, aber nur bei Vorhandensein einer JVM für die benötigte Architektur. Das Base-Image enthält die JVM als native Binärdatei. Daher ist Containerisierung nicht einfach „jar kopieren“, sondern „eine korrekte Ausführungsumgebung bereitstellen“, die die Plattform einschließt.

1
Aufgabe
Docker for Spring, Level 7, Lektion 2
Gesperrt
Plattformbericht für das Basis-Image
Plattformbericht für das Basis-Image
1
Aufgabe
Docker for Spring, Level 7, Lektion 2
Gesperrt
Build für die native Host-Plattform
Build für die native Host-Plattform
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION