Docker multi-platform images

## はじめに

docker imageのmulti-platform buildの動作確認手順を記載したもの。

https://docs.docker.com/build/building/multi-platform/


## 実施環境

- Ubuntu 24.04, x86_64
- Docker 26.1.0

## Docker Private Registry構築

docker imageの格納先となるPrivate Registryを構築する。
構築後、イメージが格納できることを確認しておく。

```shell: コマンド
$ docker run -d --name docker-registry -p 5000:5000 registry
$ docker pull busybox:latest
$ docker tag busybox:latest localhost:5000/busybox:latest
$ docker push  localhost:5000/busybox:latest
```

<details><summary>実行結果のログ</summary>

```shell: 実行結果
ubuntu@ubuntu:~/docker$ docker run -d --name docker-registry -p 5000:5000 registry
Unable to find image 'registry:latest' locally
latest: Pulling from library/registry
619be1103602: Pull complete
862815ae87dc: Pull complete
74e12953df95: Pull complete
6f0ce73649a0: Pull complete
ef4f267ce8ed: Pull complete
Digest: sha256:4fac7a8257b1d7a86599043fcc181dfbdf9c8f57e337db763ac94b0e67c6cfb5
Status: Downloaded newer image for registry:latest
14ee0d4b73c3e29eeb791630d14bd2ac249744c1dde48d42b52b71f6cbab084a
ubuntu@ubuntu:~/docker$ docker ps
CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS         PORTS
                        NAMES
14ee0d4b73c3   registry   "/entrypoint.sh /etc…"   4 seconds ago   Up 3 seconds   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   docker-registry
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ curl -k http://localhost:5000/v2/_catalog
{"repositories":[]}
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ docker pull busybox:latest
latest: Pulling from library/busybox
ec562eabd705: Pull complete
Digest: sha256:5eef5ed34e1e1ff0a4ae850395cbf665c4de6b4b83a32a0bc7bcb998e24e7bbb
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
ubuntu@ubuntu:~/docker$ docker tag busybox:latest localhost:5000/busybox:latest
ubuntu@ubuntu:~/docker$ docker images
REPOSITORY               TAG       IMAGE ID       CREATED         SIZE
registry                 latest    d6b2c32a0f14   7 months ago    25.4MB
busybox                  latest    65ad0d468eb1   12 months ago   4.26MB
localhost:5000/busybox   latest    65ad0d468eb1   12 months ago   4.26MB
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ docker push  localhost:5000/busybox:latest
The push refers to repository [localhost:5000/busybox]
d51af96cf93e: Pushed
latest: digest: sha256:28e01ab32c9dbcbaae96cf0d5b472f22e231d9e603811857b295e61197e40a9b size: 527
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ curl -k http://localhost:5000/v2/_catalog
{"repositories":["busybox"]}
ubuntu@ubuntu:~/docker$
```
</details>


## Builder instanceの作成



```shell: コマンド
$ docker run --privileged --rm tonistiigi/binfmt --install all
$ docker buildx create --name mybuilder --bootstrap --use --driver docker-container --driver-opt network=host
$ docker buildx ls
$ docker buildx inspect mybuilder
```

<details><summary>実行結果のログ</summary>

```shell: 実行結果
ubuntu@ubuntu:~/docker$ docker run --privileged --rm tonistiigi/binfmt --install all
Unable to find image 'tonistiigi/binfmt:latest' locally
latest: Pulling from tonistiigi/binfmt
8d4d64c318a5: Pull complete
e9c608ddc3cb: Pull complete
Digest: sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55
Status: Downloaded newer image for tonistiigi/binfmt:latest
installing: riscv64 OK
installing: mips64le OK
installing: mips64 OK
installing: s390x OK
installing: ppc64le OK
installing: arm64 OK
installing: arm OK
{
  "supported": [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
    "linux/ppc64le",
    "linux/s390x",
    "linux/386",
    "linux/mips64le",
    "linux/mips64",
    "linux/arm/v7",
    "linux/arm/v6"
  ],
  "emulators": [
    "qemu-aarch64",
    "qemu-arm",
    "qemu-mips64",
    "qemu-mips64el",
    "qemu-ppc64le",
    "qemu-riscv64",
    "qemu-s390x"
  ]
}
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ docker buildx create --name mybuilder --bootstrap --use --driver docker-container --driver-opt network=host
[+] Building 14.8s (1/1) FINISHED
 => [internal] booting buildkit                                                                                                                                                      14.8s
 => => pulling image moby/buildkit:buildx-stable-1                                                                                                                                   13.5s
 => => creating container buildx_buildkit_mybuilder0                                                                                                                                  1.3s
mybuilder
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ docker buildx ls
NAME/NODE        DRIVER/ENDPOINT                   STATUS    BUILDKIT   PLATFORMS
mybuilder*       docker-container
 \_ mybuilder0    \_ unix:///var/run/docker.sock   running   v0.13.2    linux/amd64, linux/amd64/v2, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
default          docker
 \_ default       \_ default                       running   v0.13.1    linux/amd64, linux/amd64/v2, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ docker buildx inspect mybuilder
Name:          mybuilder
Driver:        docker-container
Last Activity: 2024-05-19 14:35:08 +0000 UTC

Nodes:
Name:                  mybuilder0
Endpoint:              unix:///var/run/docker.sock
Driver Options:        network="host"
Status:                running
BuildKit daemon flags: --allow-insecure-entitlement=network.host
BuildKit version:      v0.13.2
Platforms:             linux/amd64, linux/amd64/v2, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
Labels:
 org.mobyproject.buildkit.worker.executor:         oci
 org.mobyproject.buildkit.worker.hostname:         ubuntu
 org.mobyproject.buildkit.worker.network:          host
 org.mobyproject.buildkit.worker.oci.process-mode: sandbox
 org.mobyproject.buildkit.worker.selinux.enabled:  false
 org.mobyproject.buildkit.worker.snapshotter:      overlayfs
GC Policy rule#0:
 All:           false
 Filters:       type==source.local,type==exec.cachemount,type==source.git.checkout
 Keep Duration: 48h0m0s
 Keep Bytes:    488.3MiB
GC Policy rule#1:
 All:           false
 Keep Duration: 1440h0m0s
 Keep Bytes:    4.657GiB
GC Policy rule#2:
 All:        false
 Keep Bytes: 4.657GiB
GC Policy rule#3:
 All:        true
 Keep Bytes: 4.657GiB
ubuntu@ubuntu:~/docker$
```
</details>

:::note info
multi-platform buildを行うための要件があり、QEMUのインストールにtonistiigi/binfmtを使用した。

> ### Strategies
> 
> You can build multi-platform images using three different strategies, depending on your use case:
> 
> 1. Using emulation, via QEMU support in the Linux kernel
> 2. Building on a single builder backed by multiple nodes of different architectures.
> 3. Using a stage in your Dockerfile to cross-compile to different architectures
:::


## Build&push

--platformオプションを使用して、x86/arm用のイメージ作成を行う。

```shell: コマンド
$ vi Dockerfile
$ docker buildx build --platform linux/amd64,linux/arm64 -t localhost:5000/test:latest --push .
```

<details><summary>実行結果のログ</summary>

```shell: 実行結果
ubuntu@ubuntu:~/docker$ docker buildx build --platform linux/amd64,linux/arm64 -t localhost:5000/test:latest --push .
[+] Building 23.3s (15/15) FINISHED                                                                                                                             docker-container:mybuilder
 => [internal] load build definition from Dockerfile                                                                                                                                  0.2s
 => => transferring dockerfile: 278B                                                                                                                                                  0.0s
 => resolve image config for docker-image://docker.io/docker/dockerfile:1                                                                                                             2.4s
 => docker-image://docker.io/docker/dockerfile:1@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e                                                              1.5s
 => => resolve docker.io/docker/dockerfile:1@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e                                                                  0.0s
 => => sha256:96918c57e42509b97f10c074d80672ecdbd3bb7dcd38c1bd95960cf291207416 11.98MB / 11.98MB                                                                                      0.6s
 => => extracting sha256:96918c57e42509b97f10c074d80672ecdbd3bb7dcd38c1bd95960cf291207416                                                                                             0.8s
 => [linux/amd64 internal] load metadata for docker.io/library/golang:alpine                                                                                                          2.7s
 => [linux/amd64 internal] load metadata for docker.io/library/alpine:latest                                                                                                          2.5s
 => [linux/arm64 internal] load metadata for docker.io/library/alpine:latest                                                                                                          2.7s
 => [internal] load .dockerignore                                                                                                                                                     0.1s
 => => transferring context: 2B                                                                                                                                                       0.0s
 => [linux/amd64 build 1/2] FROM docker.io/library/golang:alpine@sha256:f1fe698725f6ed14eb944dc587591f134632ed47fc0732ec27c7642adbe90618                                             13.1s
 => => resolve docker.io/library/golang:alpine@sha256:f1fe698725f6ed14eb944dc587591f134632ed47fc0732ec27c7642adbe90618                                                                0.1s
 => => sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B                                                                                              0.4s
 => => sha256:db27d6512129ba324acd616fd920202156dfadb8ccae8ffd1ec862bd46277c98 126B / 126B                                                                                            0.4s
 => => sha256:3e347f261431f4a9affbd78ee6f59a7486c17be94c7c8274be137aab626f0555 69.34MB / 69.34MB                                                                                      2.5s
 => => sha256:667a4259027594465efe0982aec566a89095e879511d0716b4ae322f291e393d 292.90kB / 292.90kB                                                                                    0.6s
 => => extracting sha256:667a4259027594465efe0982aec566a89095e879511d0716b4ae322f291e393d                                                                                             0.2s
 => => extracting sha256:3e347f261431f4a9affbd78ee6f59a7486c17be94c7c8274be137aab626f0555                                                                                             9.9s
 => => extracting sha256:db27d6512129ba324acd616fd920202156dfadb8ccae8ffd1ec862bd46277c98                                                                                             0.0s
 => => extracting sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1                                                                                             0.0s
 => [linux/arm64 stage-1 1/2] FROM docker.io/library/alpine:latest@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b                                            1.6s
 => => resolve docker.io/library/alpine:latest@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b                                                                0.1s
 => => sha256:bca4290a96390d7a6fc6f2f9929370d06f8dfcacba591c76e3d5c5044e7f420c 3.35MB / 3.35MB                                                                                        0.5s
 => => extracting sha256:bca4290a96390d7a6fc6f2f9929370d06f8dfcacba591c76e3d5c5044e7f420c                                                                                             0.8s
 => [linux/amd64 stage-1 1/2] FROM docker.io/library/alpine:latest@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b                                            1.5s
 => => resolve docker.io/library/alpine:latest@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b                                                                0.1s
 => => sha256:4abcf20661432fb2d719aaf90656f55c287f8ca915dc1c92ec14ff61e67fbaf8 3.41MB / 3.41MB                                                                                        0.4s
 => => extracting sha256:4abcf20661432fb2d719aaf90656f55c287f8ca915dc1c92ec14ff61e67fbaf8                                                                                             0.8s
 => [linux/amd64 build 2/2] RUN echo "I am running on linux/amd64, building for linux/amd64" > /log                                                                                   0.8s
 => [linux/amd64->arm64 build 2/2] RUN echo "I am running on linux/amd64, building for linux/arm64" > /log                                                                            0.8s
 => [linux/amd64 stage-1 2/2] COPY --from=build /log /log                                                                                                                             0.2s
 => [linux/arm64 stage-1 2/2] COPY --from=build /log /log                                                                                                                             0.2s
 => exporting to image                                                                                                                                                                1.1s
 => => exporting layers                                                                                                                                                               0.3s
 => => exporting manifest sha256:aac2940ff03955e14222c5d6c0952829a21786fa635db7e472a17df2458c1fc5                                                                                     0.0s
 => => exporting config sha256:7d93dd4b642c38e2c06eb16dc4706963642fe9804e7bf643e71ea637824d7a69                                                                                       0.0s
 => => exporting attestation manifest sha256:475ca97131d0c82289cf017101e45fc857a7d99ea2e75cda3fb9bba727fefc8b                                                                         0.0s
 => => exporting manifest sha256:ecb899a3e0ff4527a8ca5cd5e0429ae522b9ebb35d0ce60ee7a2c8a1819ac987                                                                                     0.0s
 => => exporting config sha256:7ecdf7f0056dd4a8bf6d5e8c24bb6005171e0bda11ad01bd5170531f9f38c6c4                                                                                       0.0s
 => => exporting attestation manifest sha256:cc24a5d4575484fb6635d9fbd9973181169d23f601bd31cc12baa4773cba9e71                                                                         0.0s
 => => exporting manifest list sha256:f6be49b394a70df268599d736cb67b257fb9b54ca332d67eb4694b22e6871ca1                                                                                0.0s
 => => pushing layers                                                                                                                                                                 0.3s
 => => pushing manifest for localhost:5000/test:latest@sha256:f6be49b394a70df268599d736cb67b257fb9b54ca332d67eb4694b22e6871ca1                                                        0.2s
ubuntu@ubuntu:~/docker$
```
</details>

:::note info
Dockerfileは、下記に記載されているCross compile用のサンプルをそのまま使用した。

- https://docs.docker.com/build/building/multi-platform/#cross-compilation
:::

## 動作確認

作成したイメージをそれぞれ起動し指定されたアーキテクチャーのイメージになっているか確認する。
docker run実行時に`--platform`オプションを指定してx86/armのイメージを起動する。

```shell: コマンド
$ docker run --rm --platform linux/amd64 localhost:5000/test:latest uname -m
$ docker run --rm --platform linux/arm64 localhost:5000/test:latest uname -m
```
<details><summary>実行結果のログ</summary>

```shell: 実行結果
ubuntu@ubuntu:~/docker$ docker run --rm --platform linux/amd64 localhost:5000/test:latest uname -m
Unable to find image 'localhost:5000/test:latest' locally
latest: Pulling from test
4abcf2066143: Already exists
a31561bca027: Pull complete
Digest: sha256:f6be49b394a70df268599d736cb67b257fb9b54ca332d67eb4694b22e6871ca1
Status: Downloaded newer image for localhost:5000/test:latest
x86_64
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$
ubuntu@ubuntu:~/docker$ docker run --rm --platform linux/arm64 localhost:5000/test:latest uname -m
Unable to find image 'localhost:5000/test:latest' locally
latest: Pulling from test
bca4290a9639: Pull complete
d1eef5f67990: Pull complete
Digest: sha256:f6be49b394a70df268599d736cb67b257fb9b54ca332d67eb4694b22e6871ca1
Status: Downloaded newer image for localhost:5000/test:latest
aarch64
ubuntu@ubuntu:~/docker$
```
</details>


## Cleanup

使用したリソースを削除する。

```shell: コマンド
$ docker rm -f docker-registry
$ docker run --privileged --rm tonistiigi/binfmt --uninstall "qemu-*"
$ docker system prune -a
$ docker system prune --volumes -f
```