docker buildx

Quick Start

docker buildx create --name builder --platform linux/amd64,linux/arm64 --use
docker buildx build --push -t robota.azurecr.io/mactemp .
docker buildx rm builder

TLDR

Dockerfile

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html

Сборка as is (то что мы обычно делаем):

docker build -t mactemp .

Сборка под конкретную платформу:

docker build --platform=linux/amd64 -t mactemp .

Сборка и публикация под множество платформ сразу:

docker buildx create --name builder --platform linux/amd64,linux/arm64 --use
docker buildx build --push -t robota.azurecr.io/mactemp .
docker buildx rm builder

Сборка под конкретную платформу средствами buildx (аля то же самое что и сборка as is)

docker buildx create --name builder --use
docker buildx build --load --platform linux/amd64 -t robota.azurecr.io/mactemp .
docker buildx rm builder

Дальше, заметка с деталями, что, как, почему…

Проблема: частенько, пытаясь разобраться в какой либо поломке, делаешь docker run my-awesome-service и сталкиваешься с image was build for amd64 planform, и приехали, это нужно или искать тот же коммит и собирать образ заново, не факт что это будет то же самое что и в проде и в целом на ровном месте целое приключение

Хотелка: было бы круто, если бы образ собирался сразу под amd64 и arm64, что бы его можно было гонять и в кубере, и локально на маке

Как это работает?

Имя следующий Dockerfile:

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html

Мы можем собрать его под свою платформу вот так:

docker build -t mactemp1 .

Или под целевую платформу вот так:

docker build --platform=linux/amd64 -t mactemp2 .

Но при этом это два отдельных образа

И вот что интересно, если посмотреть на страничку docker hub для того же nginx, увидим что образ поддерживает сразу несколько платформ

image-20250616-083306.png

Но при этом, вытягивая образ и делая инспецию видим следующее:

image-20250616-083440.png

Примечание: в docker hub образ числиться как поддерживающий сразу N платформ, при этом вытянув образ локально - мы видим arm64

Два ключевых момента, которые расставляют все по своим местам:

  1. docker engine (который локальный docker) поддерживает и работает с одной платформой, она может быть разной, но не может быть больше одной платформы одновременно
  2. docker registry (который docker hub, azure container registry, …) позволяют под одним и тем же URL хранить N образов под разными платформами
  3. docker pull вытягивает образ подходящий под текущую платформу, но позволяет передавать флаг --platform=linux/amd64
  4. docker pull не может вытянуть образа под все платформы, т.к. в локальном docker engine они по сути перетрут друг друга - docker engine не умеет держать образ сразу под несколько платформ - каждая платформа это отдельный образ и единственный способ иметь их локально и не перетерать - это хранить их под разными именами
  5. docker registry по сути выступает сахаром\плюшкой которая за для добства держит образа, собранные под разные платформы в одном флаке, но по сути это эдакие alias/links/references

Публикация образа

Описаное выше поясняет как работает read операции вокруг этого дела, но вопрос - как быть с create/update - как собрать и опубликовать образ, ведь исходя из того что описано выше мы могли бы сделать одно из двух:

docker build --platform=linux/amd64 -t mactemp .
docker publish mactemp

docker build --platform=linux/arm64 -t mactemp .
docker publish mactemp # will override previous image

или вот так:

docker build --platform=linux/amd64 -t mactemp1 .
docker publish mactemp1

docker build --platform=linux/arm64 -t mactemp2 .
docker publish mactemp2 # different images

Вот тут на сцену и выходит поделка buildkit и его плагин для docker - buildx

BuildX это в какой то мере docker compose - поделка позволяющая упросить рутину

Под капотом эта штука хэндлит тот факт что в docker engine нельзя иметь образ сразу под несколькоми платформами, собирает разные образа и публикует их в кучу в container registry

Пожалуй самое ключевое отличие которое нужно осознать и которое поможет понять что и как происходит дальше - образа собираются в отдельном контейнере и не доступны локально

builders

посмотреть список доступных сборщиков можно вот так:

docker buildx ls

будет показана табличка со списком доступных сборщиков, выбранный будет помечен звездочкой

переключаться между ними можно вот так:

docker buildx use [name]

проинспектировать сборщик можно вот так

docker buildx inspect # will inspect current/default builder
docker buildx inspect [name]

вот эти инспекции и вот это все нам не понадобяться и тут скорее для справки, см. дальше

В зависимости от используемого софта (colima, docker for mac, …) список сборщиков может уже содержать какие то предзаготовленные сборщики

Для чистоты экспериментов, создаем свой сборщик, что бы явно понимать что/куда/как и попытаться разобраться как оно работает

docker buildx create --name my --use --platform linux/amd64,linux/arm64

Примечания:

  • флаг --use переключает buildx на новосоздаваемый сборщик, без него, после создания нужно переключиться руками вот так docker buildx use my
  • опционально можно передать флаг --driver docker-container - это значение по умолчанию, говорит о том что сборка будет происходить в отдельном контейнере, альтернативно можно передать --driver docker - это по сути тоже самое что собирать локально докером, то есть по сути без плюшек buildx
  • по умолчанию buildx будет собирать образа под все под что только сможет, ограничить его можно передав флаг --platform linux/amd64,linux/arm64

Теперь пожалуй самое важное - стоит выполнить:

docker buildx ls
docker ps

Мы должны будем не только увидеть новый сборщик в первой команде, но и бегущий контейнер во второй

В этом ключевое отличие buildx - он собирает образа внутри контейнера, как следствие, они, не доступны локально - то есть собрав образ через buildx его нельзя будет запустить - и вот этот момент трохи взрывает мозг

publish

Если нельзя использовать собранные образа то в чем прикол, зачем это надо и как это дело используется? 🤔

Предполагается что:

  1. За для локальных экспериментов, все это нафиг не нужно, условно, если мне нужно собирать и запускать образ локально, мне нафиг не нужно куча платформ, мне нужна моя платформа, и я могу просто сделать docker build -t mactemp . и все
  2. В случае сборки под мультиплатформы, нам опять же локально оно не нужно, т.к. докер так не умеет и как следствие использование buildx предпологает сборку+публикацию в один шаг, а именно:
docker buildx build --push -t robota.azurecr.io/mactemp .

Профит в том что на деле - оно не такое страшное и ужастное, просто трохи не привычное, но это очень быстро проходит после сборки пары образов начинаешь въезжать в это дело

cleanup

После сборки и публикации образа, в зависимости от обстоятельств мы можем хотеть почистить за собой, ведь контейнер сборщика все так же работает

Тут два варианта:

stop - стопнуть контейнер сборщика, он автоматически запуститься при следующей сборке

docker buildx stop my

remove - удалить сборщик, соотв освободиться дисковое пространство, но при следующем использовании будет нужно его заново создать

docker buildx rm my

Раз уж упомянуто дисковое пространство стоит отметить что образа собираются внутри контейнера и не доступны снаружи, по чуть чуть там нособирается куча кеша, слоев, базовых образов и вот этого всего и весь прикол в том что привычные docker system prune нифига их не почистят, т.к. просто напросто не знают о них

Для этого есть две вот такие команды:

docker buildx prune # remove build cache (aka docker system prune)
docker buildx du # disk usage (aka docker system df)

github actions

В github actions нам необходимы следующие prerequisites

- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3

где:

  • qemu - утилита для виртуализации, с помощью нее создаются виртуалки под разные платформы
  • buildx - убедиться что установлен плагин и доступна команда docker buildx

Так же, нам необходимо авторизировать docker в целевом container registry, это out of scope этой заметки, но в самом просто случае, если использовать старые добрые username и password это может быть похожим на:

- uses: docker/login-action@v3
  with:
    registry: mac.azurecr.io
    username: mac
    password: abracadabra

И по большому счету, это все, на деле, предзаготовленный сборщик уже готов к работе и можно запускать публикацию и сборку as easy as:

- run: docker buildx build --push --platform linux/amd64,linux/arm64 -t robota.azurecr.io/mactemp:latest .

То есть тут даже не нужны никакие супер хитрые action'ы и\или настройки, так же, можно не париться с зачисткой сборщиков, т.к. сам action эфимерен и удалиться послершения

По мимо этого можно встретить docker/build-push-action:

- uses: docker/build-push-action@v6
  with:
    context: "." # optional, context path
    file: "Dockerfile" # optional, path to docker file
    platforms: linux/amd64,linux/arm64 # optional, platforms
    push: true # without push, this step is "useless", at least to check if it builds at all
    tags: "robota.azurecr.io/mactemp:latest" # image tag
    labels: |
      org.opencontainers.image.title=mactemp
      org.opencontainers.image.source=https://github.com/rabotaua/mactemp
      org.opencontainers.image.version=1.2.0
      org.opencontainers.image.revision=192a4d4
      org.opencontainers.image.vendor=robota.ua

azure container registry

В ажурном портале, если провалиться в детали образа, в его манифесте должно будет быть что то типа такого

image-20250617-063327.png

На скриншоте показан кусок манифеста в котором перечисляются платформы под которые собирался образ, в нашем случае искомые amd64 и arm64