# Motivation The idea is have an proof of concept of the [multi-architecture RFC](https://github.com/buildpacks/rfcs/pull/295) to support the creation of buildpack packages and builders with multi-architecture support from the beginning # Scenario Let's use our samples repo to demonstrate the current situation ## Buildpacks Today, our samples buildpacks: - Need to keep a separate folder for each `operating system` ![Screenshot 2024-02-29 at 9.18.08 AM](https://hackmd.io/_uploads/HyNHNzA2T.png) - Do not specify their supported `architecture` - When publising to a registry, we need to use different names or tags to differentiate between platforms. ![Screenshot 2024-02-29 at 9.32.28 AM](https://hackmd.io/_uploads/rJ3kUzC2a.png) ### Let's check the proposed improvements Based on the [multi-architecture RFC](https://github.com/buildpacks/rfcs/pull/295) we proposed a new folder structure to organize the binaries by platform, also we will add a way to specify all the `os/architecture` we want to support and `pack` will take care creating each individual OCI image and the Image Index to combine them. We organized our [hello-world](https://github.com/buildpacks/samples/tree/main/buildpacks/hello-world) buildpack folder structure to has different binaries for each platform and we also put together the `windows` binaries ```bash ➜ hello-world git:(main) ✗ tree . . ├── CHANGELOG.md ├── LICENSE ├── README.md ├── buildpack.toml ├── linux │ ├── amd64 │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── bin │ │ │ ├── build │ │ │ └── detect │ │ └── some-amd64-file │ └── arm64 │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── build │ │ └── detect │ └── some-arm64-file └── windows ├── README.md └── bin ├── build.bat └── detect.bat ``` In this case `CHANGELOG.md`, `LICENSE` and `README.md` files on each linux folder are hardlinks to the one saved in the root directory. We also updated the `buildapck.toml` to include `targets` according to the RFC ```toml!= # Buildpack API version api = "0.10" # Buildpack ID and metadata [buildpack] id = "samples/hello-world" version = "0.0.1" name = "Hello World Buildpack" homepage = "https://github.com/buildpacks/samples/tree/main/buildpacks/hello-world" # Targets the buildpack will work with [[targets]] os = "linux" arch = "amd64" [[targets]] os = "linux" arch = "arm64" [[targets]] os = "windows" arch = "amd64" [[targets]] os = "windows" arch = "arm64" # Stacks (deprecated) the buildpack will work with [[stacks]] id = "*" ``` Then we execute the following command ```bash! > pack buildpack package jbustamantevmware/cnb-hello-world --verbose --publish ``` With the following output: ```bash!= Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-world/linux/amd64 Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-world/linux/arm64 Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-world/windows Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-world/windows Successfully published package jbustamantevmware/cnb-hello-world and saved to registry ``` What just happened? As we are creating a buildpack package with the `--publish` flag enabled, it means we want to push the images to a registry, `pack` read the `buildpack.toml` and find several `targets`, `pack` is going to try to create multiple OCI images, one for each target, and create an `Image Index` in front of them. Notice, how `pack` will determine the **root platform folder** for each `target` and use it to create each buildpack package with the correct content. Using a tool like [crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane) we can explore the [Image Index](https://github.com/opencontainers/image-spec/blob/v1.1.0/image-index.md) created ```json { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 425, "digest": "sha256:2f0715137552e8e7444d421ae92944a0f0d2d8052ef926c7439cf296cef49928", "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 425, "digest": "sha256:45100d46883330a1e4485b6d3c248be3ada0407f6aaccf2bc60fdff5d8f8d22a", "platform": { "architecture": "arm64", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 586, "digest": "sha256:82d4996fd1f91d72074cb5f794f970530c9a6ef7b10edec0f85f2f9aaf3467a1", "platform": { "architecture": "amd64", "os": "windows" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 586, "digest": "sha256:15ce49e82bd0588ffaa5b982d85216ee15d1904f3a2f9663bdf40e3872c602d9", "platform": { "architecture": "arm64", "os": "windows" } } ] } ``` And a tool like [dive](https://github.com/wagoodman/dive) will show us the content of the OCI image, for example, the one for `linux/arm64` ![Screenshot 2024-02-29 at 2.59.30 PM](https://hackmd.io/_uploads/S1yVMwRnT.png) Let's consider another example, in this case let's take a look at our [hello-moon](https://github.com/buildpacks/samples/tree/main/buildpacks/hello-moon) sample buildpack ```bash ➜ hello-moon git:(main) ✗ tree . . ├── buildpack.toml ├── linux │ ├── README.md │ └── bin │ ├── build │ └── detect └── windows ├── README.md └── bin ├── build.bat └── detect.bat ``` Using a `buildapck.toml` similar to this one ```toml!= # Buildpack API version api = "0.10" # Buildpack ID and metadata [buildpack] id = "samples/hello-moon" version = "0.0.1" name = "Hello Moon Buildpack" homepage = "https://github.com/buildpacks/samples/tree/main/buildpacks/hello-moon" sbom-formats = ["application/vnd.cyclonedx+json"] # Targets the buildpack will work with [[targets]] os = "linux" arch = "amd64" [[targets]] os = "linux" arch = "arm64" [[targets]] os = "windows" arch = "amd64" [[targets]] os = "windows" arch = "arm64" # Stacks (deprecated) the buildpack will work with [[stacks]] id = "*" ``` And running the following command ```bash > pack buildpack package jbustamantevmware/cnb-hello-moon --verbose --publish ``` We will get an output similar to this one: ```bash!= Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-moon/linux Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-moon/linux Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-moon/windows Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-moon/windows Successfully published package jbustamantevmware/cnb-hello-moon and saved to registry ``` Similar to our previous example, `pack` will determine the **platform root folder** for each `os/arch` and create and push the appropiate OCI image. The last example we want to check is: **How do we create a multi-archicture composite buildpack?**, let's check our [hello-universe]() buildpack. In this case, there is not change to the current folder structure for a composite buildpack. ```bash ➜ hello-universe git:(main) ✗ tree . . ├── README.md ├── buildpack.toml └── package.toml ``` But we need to declare our `targets` in the `package.toml` file, similar to what we did before, something like: ```toml!= [buildpack] uri = "." # Targets the buildpack will work with [[targets]] os = "linux" arch = "amd64" [[targets]] os = "linux" arch = "arm64" [[targets]] os = "windows" arch = "arm64" [[targets]] os = "windows" arch = "amd64" [[dependencies]] uri = "docker://jbustamantevmware/cnb-hello-world:latest" [[dependencies]] uri = "docker://jbustamantevmware/cnb-hello-moon:latest" ``` For this example, the `buildpack.toml` looks like ```toml!= # Buildpack API version api = "0.10" # Buildpack ID and metadata [buildpack] id = "samples/hello-universe" version = "0.0.1" name = "Hello Universe Buildpack" homepage = "https://github.com/buildpacks/samples/tree/main/buildpacks/hello-universe" # Order used for detection [[order]] [[order.group]] id = "samples/hello-world" version = "0.0.1" [[order.group]] id = "samples/hello-moon" version = "0.0.1" ``` When creating our `buildpack package` using the following command ```bash! > pack buildpack package jbustamantevmware/cnb-hello-universe --verbose --publish --config ./package.toml ``` An output like this is shown ```bash!= Warning: A new '--target' flag is available to set the platform for the buildpack package, using 'linux' as default Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-universe Downloading buildpack dependency for platform linux/amd64 Downloading buildpack from image: jbustamantevmware/cnb-hello-world:latest Downloading buildpack dependency for platform linux/amd64 Downloading buildpack from image: jbustamantevmware/cnb-hello-moon:latest Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-universe Downloading buildpack dependency for platform linux/arm64 Downloading buildpack from image: jbustamantevmware/cnb-hello-world:latest Downloading buildpack dependency for platform linux/arm64 Downloading buildpack from image: jbustamantevmware/cnb-hello-moon:latest Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-universe Downloading buildpack dependency for platform windows/arm64 Downloading buildpack from image: jbustamantevmware/cnb-hello-world:latest Downloading buildpack dependency for platform windows/arm64 Downloading buildpack from image: jbustamantevmware/cnb-hello-moon:latest Downloading buildpack from URI: file:///Users/jbustamante/go/src/github.com/buildpacks/samples/buildpacks/hello-universe Downloading buildpack dependency for platform windows/amd64 Downloading buildpack from image: jbustamantevmware/cnb-hello-world:latest Downloading buildpack dependency for platform windows/amd64 Downloading buildpack from image: jbustamantevmware/cnb-hello-moon:latest Successfully published package jbustamantevmware/cnb-hello-universe and saved to registry ``` We also get an [Image Index](https://github.com/opencontainers/image-spec/blob/v1.1.0/image-index.md) created for all of our `targets` architectures and if we check one of them using [dive](https://github.com/wagoodman/dive), for example `linux/amd64`, we get something like ![Screenshot 2024-02-29 at 4.02.15 PM](https://hackmd.io/_uploads/HJX1bd03T.png) Noticed that **hello-world** buildpack actually have the layer that corresponds to the `amd64` architecture ## Builders Awesome, now we are able to package our buildpacks with multi-architecture in mind, `pack` creates a [Image Index](https://github.com/opencontainers/image-spec/blob/v1.1.0/image-index.md) for us and we have a new folder structure that can help us organizing our binaries depending on our particular use case. All we need right now is to create a multi-architecture builder, **how do we do it?** When creating a builder, we need a **builder** and **run** images, tipically these images are created using a `Dockerfile`. I took the alpine image from our [samples](https://github.com/buildpacks/samples/tree/main/base-images/jammy) repository and using [docker buildx](https://docs.docker.com/build/building/multi-platform/) and a few changes in our `Dockerfiles` I managed to create a multi-arch images **Run image** ![Screenshot 2024-02-29 at 4.55.31 PM](https://hackmd.io/_uploads/HygDTOA2p.png) **Build image** ![Screenshot 2024-02-29 at 4.56.24 PM](https://hackmd.io/_uploads/H1L5TdAhp.png) Once we have all our pieces in place, multi-architecture **buildpacks**, **build image** and **run image**, let's create our multi-architecture **builder**! We need to also add `targets` into our `builder.toml`, for this particular case we only use `linux` with `amd64` and `arm64` architectures. ```toml!= # Buildpacks to include in builder [[buildpacks]] uri = "docker://jbustamantevmware/cnb-hello-universe:latest" [[order]] [[order.group]] id = "samples/hello-universe" version = "0.0.1" # Targets the buildpack will work with [[targets]] os = "linux" arch = "amd64" [[targets]] os = "linux" arch = "arm64" # Base images used to create the builder [build] image = "jbustamantevmware/cnbs-build-alpine:latest" [run] [[run.images]] image = "jbustamantevmware/cnbs-run-alpine:latest" # Stack (deprecated) used to create the builder [stack] id = "io.buildpacks.samples.stacks.alpine" build-image = "jbustamantevmware/cnbs-build-alpine:latest" run-image = "jbustamantevmware/cnbs-run-alpine:latest" ``` Then, execute the following command to create a builder ```bash!= > pack builder create jbustamantevmware/cnbs-builder-alpine --config builder.toml --publish --verbose ``` Hopefully, we will get an output similar to: ```bash!= Creating builder jbustamantevmware/cnbs-builder-alpine from build-image jbustamantevmware/cnbs-build-alpine:latest Using cached version of https://github.com/buildpacks/lifecycle/releases/download/v0.18.4/lifecycle-v0.18.4+linux.x86-64.tgz Looking up buildpack docker://jbustamantevmware/cnb-hello-universe:latest Downloading buildpack for platform: linux/amd64 Downloading buildpack from image: jbustamantevmware/cnb-hello-universe:latest Creating builder with the following buildpacks: -> samples/hello-universe@0.0.1 -> samples/hello-moon@0.0.1 -> samples/hello-world@0.0.1 Adding buildpack samples/hello-moon@0.0.1 (diffID=sha256:5a529d6800e6098922a05dfb2e1156bbca4a8a64b4cc4a302cc2d32b7bb8a9ab) Adding buildpack samples/hello-universe@0.0.1 (diffID=sha256:720806c484f17fa1b546baab38b16888f5e749a267c7975c4611c2eca7bbc69a) Adding buildpack samples/hello-world@0.0.1 (diffID=sha256:7ffc37a2c4151c83a0259390f4294751e8aca908503fcb0ad6e426da42da8b73) Creating builder jbustamantevmware/cnbs-builder-alpine from build-image jbustamantevmware/cnbs-build-alpine:latest Using cached version of https://github.com/buildpacks/lifecycle/releases/download/v0.18.4/lifecycle-v0.18.4+linux.arm64.tgz Looking up buildpack docker://jbustamantevmware/cnb-hello-universe:latest Downloading buildpack for platform: linux/arm64 Downloading buildpack from image: jbustamantevmware/cnb-hello-universe:latest Creating builder with the following buildpacks: -> samples/hello-universe@0.0.1 -> samples/hello-moon@0.0.1 -> samples/hello-world@0.0.1 Adding buildpack samples/hello-moon@0.0.1 (diffID=sha256:5a529d6800e6098922a05dfb2e1156bbca4a8a64b4cc4a302cc2d32b7bb8a9ab) Adding buildpack samples/hello-universe@0.0.1 (diffID=sha256:720806c484f17fa1b546baab38b16888f5e749a267c7975c4611c2eca7bbc69a) Adding buildpack samples/hello-world@0.0.1 (diffID=sha256:188afc1b5661bc89ef8a07b3e543f47d5d4eed515830334f7cec901d460c411b) Successfully created builder image jbustamantevmware/cnbs-builder-alpine Tip: Run pack build <image-name> --builder jbustamantevmware/cnbs-builder-alpine to use this builder ``` We have our builder published for the two targets we provided and we can check the [Image Index](https://github.com/opencontainers/image-spec/blob/v1.1.0/image-index.md) using [crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane) ```json { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 2481, "digest": "sha256:5454f39ed7608edcdafb3ceb54012d8c5e021675b0c7297bd13c81defa7acce7", "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 2481, "digest": "sha256:f143eabb646acd5e71ef2b8f474f49262e7c1a2aa1b12f4b6f3fd8359ca8183b", "platform": { "architecture": "arm64", "os": "linux" } } ] } ``` Remember our [hello-world](https://github.com/buildpacks/samples/tree/main/buildpacks/hello-world) buildpack? we added a particular file on it to differentiate each artifact, in the previous log, we can see how the **diffID** is different for this buildapck. When we inspect the layers using [dive](https://github.com/wagoodman/dive) we can verify the correct layers were included. ![Screenshot 2024-02-29 at 5.15.36 PM](https://hackmd.io/_uploads/S1BMzYAnp.png)