Tools

Building Multi-Platform Docker Images with Buildx

jin@catsriding.com
Apr 30, 2024
Published byJin
Building Multi-Platform Docker Images with Buildx

Multi-Platform Images Build with Docker

Building multi-platform images is one of Docker's key features, enabling applications to run efficiently across diverse operating systems and hardware architectures. For instance, if your application must support both ARM and x86 architectures, you can use Docker’s buildx feature to generate images for multiple platforms through a single build process. This approach not only simplifies development and reduces compatibility issues but also offers major advantages for deployment across cloud, mobile, and IoT environments.

When building an image on Apple Silicon (macOS) and attempting to run it on a different CPU, you may encounter a warning like this:

Terminal
WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v3) and no specific platform was requested
8e2wer1232131d4a012890aslke1q908wqlj19288301

This indicates a mismatch between the image’s platform and the host’s architecture. To resolve it, you need to explicitly build the image for the correct platform. Without a unified build strategy, you would have to rebuild the image for every distinct environment—local machine, dev server, production server, etc. To address this inconvenience, Docker introduced multi-platform image builds.

1. Docker Buildx

To improve portability across architectures, Docker provides buildx, which enables multi-architecture image builds. It extends the standard Docker build command with full BuildKit support, allowing advanced build outputs and multi-platform support.

Docker BuildKit
BuildKit is Docker’s modern image builder designed to improve build performance. It supports parallel processing, enhanced caching, secure builds, and reduced network usage. BuildKit also supports custom output formats and multiple platforms. These capabilities make it especially valuable for large projects and complex build environments, where performance and security are critical. As a result, BuildKit plays a central role in the Docker ecosystem.

1-1. Docker Buildx Commands

You can use buildx with the following syntax:

$ docker buildx [OPTIONS] COMMAND

Key commands include:

  • bake: Start builds defined in a file.
  • build: Build Docker images using a Dockerfile.
  • create: Create a new builder instance for multi-platform builds.
  • dial-stdio: Proxy stdio streams to a builder instance.
  • du: Show disk usage for the build cache.
  • inspect: Inspect the current builder instance.
  • ls: List available builder instances.
  • prune: Remove unused build cache to free disk space.
  • rm: Remove builder instances.
  • stop: Stop running builder instances.
  • use: Set the current builder instance.
  • version: Show installed Buildx version.

Additional useful options:

  • --builder: Specify the builder instance.
  • --progress: Choose a progress output mode (auto, plain, or tty).
  • --output: Define build output, e.g., type=type,dest=path.
  • --platform: Specify target platforms (comma-separated for multiple).
  • --cache-to: Push build cache to a registry.
  • --cache-from: Reuse cache from a registry.
  • --pull: Always pull the latest base image.
  • --push: Push the resulting image to a registry.

These commands and options provide fine-grained control over the build process.

1-2. Docker Multi-Platform Image Build

To begin, set up the Docker CLI to use a buildx builder instance:

Terminal
$ docker buildx create --name waves-builder
$ docker buildx use waves-builder

Then build the image:

Terminal
$ docker buildx build \
--platform {PLATFORMS} \
-t {DOCKER_USERNAME}/{IMAGE_NAME}:{TAG} .

For example:

Terminal
$ docker buildx build \
--platform linux/amd64,linux/arm64 \
-t catsriding/hello-docker:latest \
-t catsriding/hello-docker:1.0.0 \
--push .

[+] Building 193.0s (25/25) FINISHED                                                                                                                                    docker-container:waves-builder
 => [internal] load build definition from Dockerfile                                                                                                                                              0.0s
 => => transferring dockerfile: 437B                                                                                                                                                              0.0s
 => [linux/arm64 internal] load metadata for docker.io/library/gradle:8.6.0-jdk17                                                                                                                 2.2s
 => [linux/arm64 internal] load metadata for docker.io/library/openjdk:17                                                                                                                         2.8s
 => [linux/amd64 internal] load metadata for docker.io/library/gradle:8.6.0-jdk17                                                                                                                 2.2s
 => [linux/amd64 internal] load metadata for docker.io/library/openjdk:17                                                                                                                         2.8s
 => [auth] library/gradle:pull token for registry-1.docker.io                                                                                                                                     0.0s
 => [auth] library/openjdk:pull token for registry-1.docker.io                                                                                                                                    0.0s
 => [internal] load .dockerignore                                                                                                                                                                 0.0s
 => => transferring context: 2B                                                                                                                                                                   0.0s
 => [linux/amd64 stage-1 1/3] FROM docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                          11.4s
 => => resolve docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                                               0.0s
 => => sha256:a7203ca35e75e068651c9907d659adc721dba823441b78639fde66fc988f042f 187.53MB / 187.53MB                                                                                                9.0s
 => => sha256:de849f1cfbe60b1c06a1db83a3129ab0ea397c4852b98e3e4300b12ee57ba111 13.53MB / 13.53MB                                                                                                  1.0s
 => => sha256:38a980f2cc8accf69c23deae6743d42a87eb34a54f02396f3fcfd7c2d06e2c5b 42.11MB / 42.11MB                                                                                                  4.0s
 => => extracting sha256:38a980f2cc8accf69c23deae6743d42a87eb34a54f02396f3fcfd7c2d06e2c5b                                                                                                         1.5s
 => => extracting sha256:de849f1cfbe60b1c06a1db83a3129ab0ea397c4852b98e3e4300b12ee57ba111                                                                                                         0.6s
 => => extracting sha256:a7203ca35e75e068651c9907d659adc721dba823441b78639fde66fc988f042f                                                                                                         2.3s
 => [linux/amd64 build 1/4] FROM docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                     0.0s
 => => resolve docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                                       0.0s
 => [linux/arm64 build 1/4] FROM docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                     0.0s
 => => resolve docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e                                                                       0.0s
 => [internal] load build context                                                                                                                                                                 0.1s
 => => transferring context: 398.24kB                                                                                                                                                             0.0s
 => [linux/arm64 stage-1 1/3] FROM docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                          11.0s
 => => resolve docker.io/library/openjdk:17@sha256:528707081fdb9562eb819128a9f85ae7fe000e2fbaeaf9f87662e7b3f38cb7d8                                                                               0.0s
 => => sha256:fe66142579ff5bb0bb5cf989222e2bc77a97dcbd0283887dec04d5b9dfd48cfa 14.29MB / 14.29MB                                                                                                  1.1s
 => => sha256:1250d2aa493e8744c8f6cb528c8a882c14b6d7ff0af6862bbbfe676f60ea979e 186.36MB / 186.36MB                                                                                                4.8s
 => => sha256:416105dc84fc8cf66df5d2c9f81570a2cc36a6cae58aedd4d58792f041f7a2f5 42.02MB / 42.02MB                                                                                                  5.1s
 => => extracting sha256:416105dc84fc8cf66df5d2c9f81570a2cc36a6cae58aedd4d58792f041f7a2f5                                                                                                         1.7s
 => => extracting sha256:fe66142579ff5bb0bb5cf989222e2bc77a97dcbd0283887dec04d5b9dfd48cfa                                                                                                         0.3s
 => => extracting sha256:1250d2aa493e8744c8f6cb528c8a882c14b6d7ff0af6862bbbfe676f60ea979e                                                                                                         2.6s
 => CACHED [linux/amd64 build 2/4] WORKDIR /app                                                                                                                                                   0.0s
 => [linux/amd64 build 3/4] COPY . .                                                                                                                                                              0.1s
 => CACHED [linux/arm64 build 2/4] WORKDIR /app                                                                                                                                                   0.0s
 => [linux/arm64 build 3/4] COPY . .                                                                                                                                                              0.1s
 => [linux/arm64 build 4/4] RUN gradle clean build --no-daemon                                                                                                                                   26.1s
 => [linux/amd64 build 4/4] RUN gradle clean build --no-daemon                                                                                                                                  151.0s
 => [linux/arm64 stage-1 2/3] WORKDIR /app                                                                                                                                                        0.2s
 => [linux/amd64 stage-1 2/3] WORKDIR /app                                                                                                                                                        0.0s
 => [linux/arm64 stage-1 3/3] COPY --from=build /app/build/libs/*.jar /app/application.jar                                                                                                        0.0s
 => [linux/amd64 stage-1 3/3] COPY --from=build /app/build/libs/*.jar /app/application.jar                                                                                                        0.0s
 => exporting to image                                                                                                                                                                           38.5s
 => => exporting layers                                                                                                                                                                           0.5s
 => => exporting manifest sha256:f6a344e9f8c24268f5bd7340de64b22106b952491a34c7b4d84e9f70f5d2ec82                                                                                                 0.0s
 => => exporting config sha256:3136868ba0c53a71ec1b4aa0f2db6acdf23bf29f3875ac0e368d5538e5674e9e                                                                                                   0.0s
 => => exporting attestation manifest sha256:84ff71291fa072ef3a2b5d33f6047435897a6f88e131989c826fbcedf2bc6fa8                                                                                     0.0s
 => => exporting manifest sha256:75c4fda6ab7aa28f571e79db3eed54c083e0792559662516efec5d8bc9b9b5b4                                                                                                 0.0s
 => => exporting config sha256:e06d71be7d33dcb653966a1b8f9c9779889fed327d8330ba933055fdbe0bf87a                                                                                                   0.0s
 => => exporting attestation manifest sha256:73902fdf9d077b7ea3ec18646a2ba86b1ae4b5013ba868639c291f13f68f24ba                                                                                     0.0s
 => => exporting manifest list sha256:e788a4906c2053685d3458cbce44487483cd1597fa791a025208f93da5d5bfda                                                                                            0.0s
 => => pushing layers                                                                                                                                                                             2.5s
 => => pushing manifest for docker.io/catsriding/hello-docker:latest@sha256:e788a4906c2053685d3458cbce44487483cd1597fa791a025208f93da5d5bfda                                                      3.2s
 => => pushing manifest for docker.io/catsriding/hello-docker:1.0.0@sha256:e788a4906c2053685d3458cbce44487483cd1597fa791a025208f93da5d5bfda                                                       2.0s
 => [auth] catsriding/hello-docker:pull,push token for registry-1.docker.io                                                                                                                       0.0s

View build details: docker-desktop://dashboard/build/waves-builder/waves-builder0/p9m665wfrsfjt6vhybzlbk4qt

Build multi-platform images faster with Docker Build Cloud: https://docs.docker.com/go/docker-build-cloud

This command builds the image for both linux/amd64 and linux/arm64, and pushes them to Docker Hub.

arm64 vs. amd64
arm64 refers to 64-bit processors based on the ARMv8 architecture, widely used in mobile devices and now in Apple M1/M2 chips.
amd64 (also known as x86-64) was developed by AMD and is supported by most desktop and laptop CPUs from both AMD and Intel.

To verify that the image was built correctly, use docker manifest inspect:

Terminal
$ docker manifest inspect {DOCKER_USERNAME}/{IMAGE_NAME}:{TAG}

You should see something like this:

Terminal
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.oci.image.index.v1+json",
   "manifests": [
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 1249,
         "digest": "sha256:f6a344e9f8c24268f5bd7340de64b22106b952491a34c7b4d84e9f70f5d2ec82",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 1249,
         "digest": "sha256:75c4fda6ab7aa28f571e79db3eed54c083e0792559662516efec5d8bc9b9b5b4",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

You can also confirm on Docker Hub, where the image will show a MULTI-PLATFORM badge and a list of supported OS/ARCH combinations.

multi-platform-images-build-with-docker_00.png

1-3. Docker Multi-Platform Image Pull

When pulling multi-platform images, Docker automatically selects the correct variant based on your host environment. If you want to explicitly pull for a specific platform, use:

Terminal
$ docker pull {DOCKER_USERNAME}/{IMAGE_NAME}:{TAG} --platform={PLATFORM}

For example, to pull the x86 version on an ARM machine:

Terminal
$ docker pull catsriding/hello-docker:latest --platform=linux/amd64

This allows you to fetch exactly the image you need for a specific architecture.

2. Conclusion

Multi-platform image support is one of Docker’s most powerful features, enabling consistent application execution across various operating systems and CPU architectures. By using buildx, developers can create images for multiple platforms through a single build process, simplifying both development and deployment.