Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як реалізувати multi-platform builds (ARM + AMD64)?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Бери **`docker buildx`** з `--platform`-флагом, що перелічує target-архітектури. Buildx робить **manifest list**, яка теґає одне image-ім'я через кілька архітектур; daemon вибирає правильну при pull. ```bash # Один раз: створи builder, що підтримує multi-arch docker buildx create --name multi --use docker buildx inspect --bootstrap # Build і push для двох платформ за одну команду docker buildx build \ --platform linux/amd64,linux/arm64 \ -t myorg/app:1.0 --push . ``` **Головне:** `--push` обов'язковий (multi-arch images не живуть у локальному image-store). Емуляція через QEMU повільна; для прод-у реєструй **native ARM-builder'и** (окрема машина) і нехай buildx розкидає per-arch build'и.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Multi-platform Docker images** це images, побудовані один раз і теґнуті під одним іменем, але містять layer'и, скомпільовані під кілька CPU-архітектур (наприклад, `linux/amd64` і `linux/arm64`). Daemon прозоро pull'ить правильний варіант на runtime. Це важливо, бо Apple Silicon Mac, AWS Graviton-instances і Raspberry Pi всі на ARM, тоді як більшість laptop'ів і CI-runner'ів на x86-64. ## Теорія ### TL;DR - Бери `docker buildx build --platform linux/amd64,linux/arm64 -t name --push .` - Output це **manifest list**: один tag, що показує на N per-arch images. - Buildx за замовчуванням бере **QEMU-емуляцію** для non-native архітектур. Повільно, але працює. - Для швидкості setup'и **native builder'и** per architecture (реальна ARM-машина). - Не можна зберігати multi-arch локально; треба `--push` у registry. Або використовуй `--output=oci` для OCI-bundle. - Типові болі: native-deps, що ламаються під емуляцією, `CGO_ENABLED=0` для Go, prebuilt-wheel'и для Python. ### Чому multi-platform Одна CPU-архітектура колись була нормою. Сьогодні: - **Apple Silicon (M1/M2/M3)** девелопери крутять ARM локально. - **AWS Graviton, Ampere, Oracle ARM** дають 30-40% кращий price/performance. - **Raspberry Pi, IoT, edge** потребують ARM-32 або ARM-64. - **Server-farm'и** ще переважно x86-64. Якщо твоя image тільки `linux/amd64`, Apple Silicon-dev, що pull'ить її, отримає прозору QEMU-емуляцію (повільно), або `no matching manifest` помилку. Multi-arch фіксить це одним tag-ом. ### Manifest list (a.k.a. fat manifest) Registry зберігає один додатковий manifest per tag, що показує на per-arch-images: ```json { "manifests": [ { "platform": { "architecture": "amd64", "os": "linux" }, "digest": "sha256:abc..." }, { "platform": { "architecture": "arm64", "os": "linux" }, "digest": "sha256:def..." } ] } ``` Коли ти `docker pull myorg/app:1.0` на ARM-Mac, daemon читає manifest list, бере `arm64`-digest, pull'ить правильні layer'и. Той самий tag, та сама команда, різні bytes. ### Як buildx його будує Buildkit (engine за buildx) компілює кожну архітектуру в окремому context. Два варіанти: 1. **Один builder + QEMU**. Buildkit реєструє `binfmt_misc`, щоб транслювати non-native бінарі через QEMU. Host крутить `RUN`-інструкції для кожної платформи через емуляцію. Працює всюди, повільно для скомпільованих мов. 2. **Кілька native-builder'ів**. Buildkit розкидає `arm64`-роботу на реальну ARM-машину, `amd64`-роботу на x86-машину. Швидко, потребує інфраструктури. ## Приклади ### Швидкий старт: емульований multi-arch ```bash # Enable QEMU один раз per host (Docker Desktop робить це автоматично) docker run --privileged --rm tonistiigi/binfmt --install all # Створи buildx-builder, що використовує docker-container driver docker buildx create --name multi --driver docker-container --use docker buildx inspect --bootstrap # Build для двох платформ, push у registry docker buildx build \ --platform linux/amd64,linux/arm64 \ -t myorg/app:1.0 \ --push \ . ``` `--push` обов'язковий, бо multi-arch images не живуть у локальному Docker-image-store (він індексує by single arch). Build пушить обидві архітектури і manifest list за один крок. Верифікуй: ```bash docker buildx imagetools inspect myorg/app:1.0 # Manifest: docker.io/myorg/app:1.0@sha256:... # MediaType: application/vnd.oci.image.index.v1+json # Manifests: # linux/amd64 # linux/arm64 ``` ### CI-friendly: GitHub Actions ```yaml name: build on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v3 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} - uses: docker/build-push-action@v5 with: platforms: linux/amd64,linux/arm64 push: true tags: myorg/app:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max ``` `setup-qemu-action` реєструє binfmt; `cache-from: type=gha` переюзає GitHub Actions cache між run'ами (величезний speedup). ### Native ARM-builder (production) Емуляція може бути 10x повільнішою за native. Для реальних workload-ів реєструй окрему ARM-машину: ```bash # На x86 «orchestrator»-host docker buildx create \ --name multi-native \ --node x86 --platform linux/amd64 docker buildx create \ --append \ --name multi-native \ --node arm \ --platform linux/arm64 \ ssh://user@arm-host docker buildx use multi-native docker buildx inspect --bootstrap # Build: кожна arch крутиться natively на своїй node docker buildx build --platform linux/amd64,linux/arm64 -t myorg/app:1.0 --push . ``` Тепер `linux/amd64`-робота крутиться на локальній x86, `linux/arm64`-робота на remote ARM-box; buildx merge'ить результати в один manifest list. ### Build тільки локальної платформи під час dev ```bash # Коли itera'єш, не треба обох архітектур docker buildx build --platform local -t myorg/app:dev --load . ``` `--load` затягує результат у локальний image-store (неможливо з multi-arch). Бери `--load` для dev, `--push` для release. ### Типові пастки **Native-deps ламаються під емуляцією** Python-package, що компілює C-extension, може fail під QEMU, бо build-script детектить host-glibc, але binary емулюється: ``` fatal error: Python.h: No such file or directory ``` Фікс: бери prebuilt-wheel'и (PyPI manylinux), або build natively per arch. **Go: `CGO_ENABLED` і cross-compilation** Go cross-компілює natively без емуляції: ```dockerfile FROM --platform=$BUILDPLATFORM golang:1.22 AS build ARG TARGETOS TARGETARCH COPY . /src WORKDIR /src RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o app FROM gcr.io/distroless/static COPY --from=build /src/app /app ENTRYPOINT ["/app"] ``` `$BUILDPLATFORM` це host-arch (швидко); `$TARGETPLATFORM` це output-arch. Go компілює без емуляції; лише фінальний layer per-arch. **Різні package-manager'и per arch** ```dockerfile FROM alpine:3.18 ARG TARGETARCH RUN apk add --no-cache libstdc++ # Alpine-repos auto-resolve per arch; екстра-роботи не треба. ``` Але для дистрибутивів без auto-resolution, можливо потрібно: ```dockerfile RUN case "$TARGETARCH" in \ amd64) URL=https://example.com/amd64.tar.gz ;; \ arm64) URL=https://example.com/arm64.tar.gz ;; \ esac && wget -O - "$URL" | tar xz ``` **`docker manifest` vs buildx** Старіша `docker manifest` команда може зшити existing per-arch images у manifest list вручну. Buildx автоматизує і це сучасний шлях. Бери buildx, якщо нема legacy-tooling. ## Реальне застосування - **Публічні images** на Docker Hub: ship multi-arch (Postgres, Redis, nginx всі роблять). - **Внутрішні app**: щонайменше amd64 + arm64, якщо хоч один dev на Apple Silicon або хоч один prod на Graviton. - **CI-matrix**: бери buildx + `cache-from: type=gha`, щоб не rebuild'ити обидві архітектури кожен push. - **Edge-деплої**: додавай `linux/arm/v7` для Raspberry Pi-class пристроїв. ### Питання для поглиблення **Q:** Чи можу я крутити multi-arch image без buildx? **A:** Так, daemon обробляє pull-time селекцію автоматично. Buildx потрібен лише, щоб *виробити* multi-arch images. **Q:** Наскільки повільніша QEMU-емуляція? **A:** Для скомпільованих мов (Rust, C++), 5-10x повільніша за native. Для інтерпретованих (Python, Node.js install), 2-3x. Для Go (cross-compile, без емуляції), майже безкоштовно. **Q:** Що таке `--platform=$BUILDPLATFORM`? **A:** Каже «крути цю stage на host-arch, незалежно від target». Бери для build-step, що виробляє arch-specific output (Go cross-compile). Фінальна stage використовує `$TARGETPLATFORM`, щоб assemble per-arch images. **Q:** (Senior) Як debug'ити multi-arch build, що ламається тільки для arm64? **A:** Build тільки тієї платформи (`--platform linux/arm64 --load --platform local` після switch builder'а), потім `docker run --rm -it --platform linux/arm64 myorg/app:dev sh`, щоб порисати у failed-image. Інспектуй логи з `docker buildx build --progress=plain` для verbose-output. Якщо помилка `exec format error`, ти скопіював amd64-binary у arm64-stage.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.