Multi-arch образы стали стандартом де-факто. Пользователи ждут, что ваш контейнер запустится и на классическом x86_64 (amd64), и на ARM-платформах (arm64/v8) — от хостингов до IoT и облачных ARM‑серверов. С docker buildx и BuildKit это достижимо без боли: мы сможем собирать универсальные образы локально и на CI, использовать кэш в registry, проверять итоговый манифест и оптимизировать скорость.
Зачем вам multi-arch и когда он критичен
Даже если у вас преимущественно amd64-инфраструктура, мир быстро смещается в сторону ARM. Локальные разработчики на Apple Silicon, ARM‑серверы в облаках, edge‑устройства — это уже не ниша. Универсальный образ:
- Если запускается на любом железе, снижает поддержку разных тегов.
- Упрощает CD: один артефакт для всех сред.
- Сокращает риск «всё работает у меня на x86, но падает на arm».
При этом у multi-arch есть нюансы: сборка может требовать эмуляции (QEMU), а значит — медленнее и капризнее. Поэтому важно понять, когда использовать QEMU, а когда подключать нативные билд‑ноды.
Buildx и BuildKit: что происходит под капотом
docker buildx — надстройка над BuildKit, умеет:
- Собирать для нескольких платформ с флагом
--platform. - Публиковать манифест‑листы (multi-arch) в registry.
- Кэшировать слои локально, в inline‑формате и в registry.
- Распределять сборки по нескольким билдер‑нодам (в том числе удалённым).
Включите BuildKit по умолчанию, если он не активен:
export DOCKER_BUILDKIT=1
export BUILDKIT_PROGRESS=plain
Подготовка хоста: binfmt_misc и QEMU
Чтобы на amd64‑сервере собирать образы для arm64, BuildKit использует эмуляцию через QEMU. Установить интерпретаторы проще всего контейнером tonistiigi/binfmt:
docker run --privileged --rm tonistiigi/binfmt --install all
Проверить, что эмуляторы появились:
ls -1 /proc/sys/fs/binfmt_misc | grep qemu
Если видите что-то вроде qemu-aarch64, эмуляция для arm64 готова. Важно: QEMU заметно медленнее реального ARM‑ядра, особенно на этапе компиляции. Для продакшн‑пайплайнов подумайте о нативном ARM‑билдере.
Создаём builder для buildx
Рекомендуется использовать драйвер docker-container — он даёт изолированный BuildKit и удобное управление:
docker buildx create --name multiarch --driver docker-container --use
docker buildx inspect --bootstrap
docker buildx ls
--bootstrap подтянет нужные бинарники BuildKit. С этого момента buildx готов к сборке multi-arch.

Базовый Dockerfile с учётом платформ
BuildKit прокидывает полезные переменные: TARGETPLATFORM, BUILDPLATFORM, TARGETOS, TARGETARCH, TARGETVARIANT. Используйте их, чтобы подстраивать шаги:
# syntax=docker/dockerfile:1.7
FROM --platform=$BUILDPLATFORM golang:1.22-bookworm AS builder
ARG TARGETOS
ARG TARGETARCH
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ENV CGO_ENABLED=0
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app
FROM gcr.io/distroless/base-debian12:nonroot
COPY --from=builder /out/app /usr/local/bin/app
USER nonroot
ENTRYPOINT ["/usr/local/bin/app"]
Что важно:
- Старайтесь делать сборку статичной (
CGO_ENABLED=0), чтобы избежать проблем с glibc/musl. - Избегайте Alpine, если у вас есть закрытые зависимости на glibc — иначе получите «работает на Debian, падает на Alpine».
Node.js и Python: архитектурные подводные камни
На Node.js нативные модули (node-gyp, prebuild) завязаны на архитектуру. Чтобы корректно пересобрать под целевую платформу, используйте стадии сборки и избегайте переносить node_modules между архитектурами.
# syntax=docker/dockerfile:1.7
FROM --platform=$BUILDPLATFORM node:20-bookworm AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-bookworm-slim
WORKDIR /app
COPY --from=deps /app/dist ./dist
COPY package.json ./
RUN npm ci --omit=dev
CMD ["node", "dist/index.js"]
Для Python заранее продумывайте колёса (wheels). Если требуются бинарные зависимости, убедитесь, что они собираются под целевую архитектуру. Частый кейс — компиляция cryptography, psycopg и т.п. при отсутствии подходящих manylinux wheels для arm64.
Сборка multi-arch и публикация в registry
Самый короткий путь собрать и запушить манифест‑лист:
docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/team/app:1.2.3 --push .
Для локального теста одного архитектурного образа вместо --push используйте --load (оно поддерживает только одну платформу за раз):
docker buildx build --platform linux/arm64 -t app:test --load .
Проверка манифеста
Убедиться, что в реестр ушёл именно multi-arch:
docker buildx imagetools inspect registry.example.com/team/app:1.2.3
В выводе увидите список платформ и соответствующие дайджесты. Если видите только один манифест, значит сборка не объединилась в мультиархив.
Кэширование: локально, inline и в registry
Кэш существенно ускоряет повторные сборки, особенно при QEMU. Варианты экспорта:
- local: на диск билдера.
- inline: в метаданные слоя образа.
- registry: отдельные кэш‑артефакты в реестре.
Практичный пресет: кэш в registry c максимальной детальностью и OCI‑медиатипами:
docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/team/app:1.2.3 --push --cache-to type=registry,ref=registry.example.com/team/app:cache,mode=max,oci-mediatypes=true --cache-from type=registry,ref=registry.example.com/team/app:cache .
Inline‑кэш удобен для одноузловых сценариев, но registry‑кэш легче шарить между CI‑агентами и разработчиками. Если вам интересны кэш‑маунты и ускорение зависимостей на этапе RUN, загляните в материал как работать с BuildKit cache mounts.

Где QEMU хорош, а где — лучше без него
QEMU позволяет «тащить» сборку arm64 на amd64‑хосте, но помните:
- Компиляция C/C++ и npm‑модулей с нативными зависимостями под эмуляцией существенно медленнее.
- Временные аномалии в тестах под эмуляцией не редкость.
- Долгие пайплайны увеличивают риск таймаутов и затрат на CI.
Золотой стандарт: построить распределённый builder из двух нод — amd64 и arm64. Buildx сам будет гнать соответствующие задачи на нужный узел:
docker buildx create --name fleet --driver docker-container --use
# Добавляем ARM-ноду по SSH (на реальном arm64 сервере уже должен быть установлен Docker)
docker buildx create --append --name fleet ssh://builder@arm64-ci.local
# По желанию — x86-ноду отдельно (если менеджер запущен не там)
docker buildx create --append --name fleet ssh://builder@amd64-ci.local
docker buildx inspect --bootstrap
После этого одна команда docker buildx build --platform linux/amd64,linux/arm64 ... распределит сборку между нодами без QEMU. Это быстрее и надёжнее. Если нужно быстро получить нативные узлы под CI, поднимите их на облачном VDS и подключите по SSH.
Для оценки выгоды от ARM‑инстансов под runtime и сборки можно посмотреть сравнение в статье производительность ARM/VDS для веб‑стеков.
ARG TARGETPLATFORM и условные шаги
В Dockerfile можно менять поведение в зависимости от платформы:
# syntax=docker/dockerfile:1.7
FROM debian:12-slim
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "arm64" ]; then echo arm64 specific; else echo amd64 path; fi
Так проще переключать пакеты, ссылки на артефакты, бинарники релизов и т.п.
APT и архитектуры: частые ошибки
При сборке на Debian/Ubuntu образах для arm64 на amd64‑хосте через QEMU часто ловят:
Exec format error— вы запустили бинарник не той архитектуры.E: Unable to locate packageилиWrong architecture— вы пытаетесь поставить пакет под другую архитектуру.
Советы:
- Если нужен мультиархитектурный APT, используйте
dpkg --add-architectureи указывайте суффиксы, но лучше избегайте смешивания в Dockerfile. - Берите базовый образ целевой архитектуры в каждой стадии и избегайте межархитектурного копирования бинарников.
Go: CGO, OpenSSL и кросс-компиляция
Go отлично компилируется кросс‑платформенно, но CGO ломает сказку: если ваш код тянет C‑библиотеки (например, OpenSSL), вам понадобится соответствующий компилятор и dev‑пакеты под целевую архитектуру. Проще сделать CGO‑free сборку или собирать на нативной ноде arm64.
Node.js: prebuild и node-gyp
Если вы используете модули с бинарными прелюдами, то перенос node_modules через архитектуры почти всегда приводит к невалидным бинарям. Следуйте правилу: инсталляция зависимостей должна выполняться в той стадии и на той платформе, где будет конечный артефакт. Не копируйте node_modules между платформами.
Python: wheels и manylinux
Следите за колёсами для arm64. Если авторы пакета не выложили сборки, вы попадёте в долгую компиляцию под QEMU или вовсе на ошибки. Выручает нативная arm64‑нода или альтернативные зависимости.
Registry: теги, семвер и неизменяемость
Для предсказуемых деплоев:
- Публикуйте семвер‑теги и неперезаписываемые дайджесты.
- Для «latest» делайте атомарное обновление манифеста только после успешной сборки всех платформ.
- Старайтесь не смешивать разные билды под одним тегом в одно и то же время, чтобы кэш не вводил CI в заблуждение.
Если поднимаете собственный приватный registry, не забудьте про TLS — оформить и подключить SSL-сертификаты проще заранее, чем разбираться с недоверием клиентов Docker.
Секреты, SSH и приватные зависимости
BuildKit поддерживает безопасную передачу секретов и SSH‑ключей в момент сборки. Это позволяет приватно тянуть репозитории или токены, не оставляя их в слоях. Учитывайте, что секреты доступны только во время выполнения шага, где они явно подключены.
# Пример: приватный git-доступ во время RUN
# docker buildx build --ssh default
# В Dockerfile:
# RUN --mount=type=ssh git clone git@github.com:org/private-repo.git
Отладка типичных проблем
- no matching manifest for linux/arm64/v8. Базовый образ не поддерживает arm64. Выберите другой или переключитесь на совместимую версию.
- Cannot execute binary file: Exec format error. Вы случайно перенесли бинарник между архитектурами. Собирайте и устанавливайте зависимости внутри стадии той же платформы.
- node-pre-gyp ERR или gyp ERR. Установите build‑tools и dev‑пакеты для целевой платформы или собирайте на нативной ноде arm64.
- Медленная сборка под QEMU. Вынесите тяжёлую компиляцию на нативную ноду и включите registry‑кэш.
- Разные результаты на CI и локально. Стабилизируйте версии базовых образов, используйте
--provenance=falseпри необходимости и фиксируйте версии инструментов сборки.
Миграция существующего проекта на multi-arch: пошагово
- Включите BuildKit и создайте builder с драйвером
docker-container. - Поднимите QEMU через
tonistiigi/binfmtили подключите нативную arm64‑ноду к buildx. - Разделите Dockerfile на чёткие стадии: зависимости, сборка, рантайм.
- Внедрите
ARG TARGETPLATFORM/TARGETARCHи условные шаги для различий. - Уберите перенос артефактов между архитектурами. Инсталируйте каждый набор зависимостей в своей стадии.
- Добавьте registry‑кэш и проверьте выгоду по времени.
- Соберите
--platform linux/amd64,linux/arm64, опубликуйте--push, проверьте манифестimagetools inspect. - Прогоните smoke‑тесты на обеих архитектурах (по возможности — на нативных нодах).
Практические рекомендации по производительности
- Базовые образы выбирайте стабильные и минимальные. Distroless или slim‑образы уменьшают слои и риски несовместимости.
- Максимально отделяйте скачивание зависимостей и сборку — это улучшает кэшируемость.
- Проксируйте пакетные менеджеры (npm, pip, apt) через локальный прокси‑кэш, если он доступен, чтобы повысить повторяемость и скорость.
- Для тяжёлых CI‑сценариев используйте связку из двух билд‑нод: amd64 и arm64. Так вы полностью обходите QEMU.
Надёжность и безопасность
- Не запускайте сборочные контейнеры с лишними привилегиями.
--privilegedнужен только для установки binfmt. - Уменьшайте поверхность атаки: в финальном образе оставляйте только бинарь и необходимые сертификаты/локали.
- Периодически пересобирайте образы даже без изменения кода, чтобы подтянуть security‑фиксы в базовых слоях.
Ключ к успешной multi-arch стратегии: собирать там, где это нативно и быстро, а кэш в registry плюс продуманная структура Dockerfile сгладят остальное.
Чек-лист перед релизом
- Buildx builder создан и работает с нужными нодами.
- QEMU установлен, если нет нативной arm64‑ноды.
- Dockerfile разбит на стадии, нет переносов архитектурно‑зависимых артефактов.
- Библиотеки с CGO и нативные модули собираются под нужную архитектуру.
- Включен registry‑кэш и проверена его эффективность.
- Манифест содержит
linux/amd64иlinux/arm64. - Смок‑тесты прошли на обеих архитектурах.
Итоги
docker buildx делает multi-arch рутиной: с правильной настройкой builder’а, QEMU и кэша вы получаете универсальные образы, которые одинаково предсказуемо запускаются на amd64 и arm64. Начните с простой публикации в ваш registry, проверьте манифест и постепенно улучшайте Dockerfile, кэш и стратегию сборок. Когда нагрузка вырастет — подключите нативную ARM‑ноду к buildx и забудьте про «медленную эмуляцию».


