First attempt at a multi-arch buildpack

Featured image

My previous builder generated some fabulous feedback, including an issue in GitHub. It also opened the door for some wonderful collaborations while I have been traveling to Barcelona and Tel Aviv.

On this journey of trying to get ARM64 support into the upstream Paketo buildpacks I have learned so much. The next step is to prove that we can create the pipelines that can deliver a multi-architecture image.

Skip to the Quick Start here

Docker manifest

In order to see the architecture of an image, you can use the docker manifest command.

# Inspect shows the image architecture
docker manifest inspect --verbose dashaun/java-native-builder-arm64:7.37.0

Inspecting the ARM64 builder that was created in earlier article

Create a new manifest list

The process for slapping docker images together is pretty simple. Ideally you should be using the “same” image, but we are just proving a point here.

My theory is that if I provide an arm64 architecture and an amd64 architecture to the same manifest list, that it will work as expected.

# create a new manifest with a name and tag
docker manifest create dashaun/java-native-builder-multiarch:7.37.0 \
# add ARM64 builder
--amend dashaun/java-native-builder-arm64:7.37.0 \
# add Paketo tiny builder for AMD64
--amend paketobuildpacks/builder:tiny

# push the manifest to the repository
docker manifest push dashaun/java-native-builder-multiarch:7.37.0

That’s it!

inspect the manifest list - the builder

Now you can inspect the manifest and see that it has packaged ARM64 and AMD64 architectures. This capability has been available for years. It is also surprisingly simple to accomplish as you can see.

docker manifest inspect --verbose dashaun/java-native-builder-multiarch:7.37.0
[
	{
		"Ref": "docker.io/dashaun/java-native-builder-arm64:7.37.0",
		"Descriptor": {
			"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
			"digest": "sha256:80771c9c2efee25020ba60e320c41cc385a333434ad2982ed7326a0a413b9ae7",
			"size": 5150,
			"platform": {
				"architecture": "arm64",
				"os": "linux"
			}
		},
		"SchemaV2Manifest": {
			"schemaVersion": 2,
			"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
			"config": {
				"mediaType": "application/vnd.docker.container.image.v1+json",
				"size": 12587,
				"digest": "sha256:8cd1ead83689b853055ac2de0cc07c5f3c9d8d087b2ac97e838551fe8e962e00"
			},
			"layers": [
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 27195998,
					"digest": "sha256:4e7e0215f4adc2c48ad9cb3b3781e21d474b477587f85682c2e2975ae91dce9d"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 4348,
					"digest": "sha256:fc2e670f062f6edbb1b3be6aae88cbc0978a5273314c365bc4e99d861dfb5ff1"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 103281689,
					"digest": "sha256:cba3892e6fcca4b47983803d53b777bc05476639fcfc1a2d4f384eac40363677"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 179,
					"digest": "sha256:f5230b5f167cfc41fb9f96d53de0d9c5832e3b03e3a705052cf42ceada13aea9"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 197,
					"digest": "sha256:357fefdf9bc907107a38600cf8d79c713346dc97370273d1aa79635d97a2f6f9"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 8870380,
					"digest": "sha256:0ab7ccc1e1a42589b53d1397672009a979b296933efc276d79d3a2cdc336656c"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2498256,
					"digest": "sha256:fa161ac7a0015773c3f6890d6f1824957a6e21cee5a9bbf8ddc60e4e6d30a3ed"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1806073,
					"digest": "sha256:bdfdd20caee6a7044607d11d9a4f758a007ad2d61399ea5e3db22a7037ab4975"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3273385,
					"digest": "sha256:bc251569386a8e5fd513c85d9c51e76a258f0e59d0f945e185e101930012c9db"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1509592,
					"digest": "sha256:caeca36964d2da6c2b88abd998281e4fd5b708cfdd1440486f6acf7bc569292e"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3191207,
					"digest": "sha256:f840d9ee444653d04a2181d1d99e4f579d01b56349cce9325c6dade9af410998"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3273637,
					"digest": "sha256:b4f56cede28073295dc750e93678876a55b9fdafd97c940b97dc9a01ae345663"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2387292,
					"digest": "sha256:cebaf5f7c6e3350f5973f2c77f9ba90138d5a6bb2f1eb9e86084c8cf9f6117c6"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 4208510,
					"digest": "sha256:47be0bf349071cb594970ef2d18f383b91231645ba49f27b5b39b47d9090aedc"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1818619,
					"digest": "sha256:5844db3d59f2bdc13aa19a6fd09789cf4a12aa87088759840a79c10eb01f93c5"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1569855,
					"digest": "sha256:89975d6b7daf9b8cf8b1a8350461e9e009b0ffc2a482becb9e1073adc3475153"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2413551,
					"digest": "sha256:aca3b8db4220fa7d69bf9cbd0bebc048cfa381174929828affb820c5b6b0f505"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2480839,
					"digest": "sha256:71bd4e702bfc97f1db60e6a5ac364897b2818dc2f92f1bf9d78c2935a1ae125d"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2521983,
					"digest": "sha256:3e1f506f69017dbcedb2a2b022ff0d6e63d4c0cd44e32fdcf6cc9e39b2416ade"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2475996,
					"digest": "sha256:f617bf8af0912672e16ed61332d6e7ad1f0ffc4fffe049da45ea670a1c441afd"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 362,
					"digest": "sha256:0e3fabdd36281c4c1c946d09854510dc82ffd61476d62aaa474b3c64d8969ae3"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 151,
					"digest": "sha256:9602e7831cd64261f173d4789e26f9cada67107ba1688409f7781bd737792b58"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 32,
					"digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"
				}
			]
		}
	},
	{
		"Ref": "docker.io/paketobuildpacks/builder:tiny",
		"Descriptor": {
			"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
			"digest": "sha256:e5b4e4e720598e905baa793a94259ee75827949a6ba22a3d3196eb332e475dd6",
			"size": 9773,
			"platform": {
				"architecture": "amd64",
				"os": "linux"
			}
		},
		"SchemaV2Manifest": {
			"schemaVersion": 2,
			"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
			"config": {
				"mediaType": "application/vnd.docker.container.image.v1+json",
				"size": 64488,
				"digest": "sha256:94a3f804e111904682577130709e0cac6e0541e6b1931c1f9aa4f27326c974fc"
			},
			"layers": [
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 29589398,
					"digest": "sha256:38496a9cab3e2bb43fa8ae1d45c3ddc1d2f89328e1f3973f029949b8cb2dd5fe"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 247,
					"digest": "sha256:2e7826560210e2f0d4249f9ea20651fecd45306c2482767274c8c37289c23788"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 316,
					"digest": "sha256:6013ff26a0374342ff4848b3171ddbe396413d80faa2b1ee67ca9afbfa64cafc"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 106179950,
					"digest": "sha256:87196f2fd40c0ee5587ff771878aad41b42c1d29628c75fff9206b5c0b77f016"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 188,
					"digest": "sha256:d61afba38b8e093dfc46b8e81c141b2d7eddc0903b0fc530b3af36a46a144af7"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2744757,
					"digest": "sha256:856e5ee7330534a520c3fd1d90260e9a5e99bfc182e0c2c3b18f7fea06f0bb0e"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 807,
					"digest": "sha256:7eededf5887404e40dfc0d1d533fb9db3fe6b725213e4f3edda1ca1f5b1492c4"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 197,
					"digest": "sha256:357fefdf9bc907107a38600cf8d79c713346dc97370273d1aa79635d97a2f6f9"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 9753557,
					"digest": "sha256:d0d113e4e2bd21dbaa2f66e687ec4a23e4e83331b7845b85d0c10de1da05b08d"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2574003,
					"digest": "sha256:41869a3aefd1a5bf8db003eb401d1af5ef2568c92262c3038f553d38145f36a1"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2573999,
					"digest": "sha256:053ee41df41d6484ffec64fda64937a6418a1404f602487019fccddbad2958de"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 4641532,
					"digest": "sha256:0fec499a25de5a35aa814bed46943a93c94d0e6785eedc679874ec3da04bc748"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2525673,
					"digest": "sha256:c7b3308e13d3f30bde70d2293590aefdb95e52784b8bb840e13c587d354604e6"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 8714351,
					"digest": "sha256:51918c870f73c8ceffc527e79041e08d3101beeabd0c0bc44555b6faf1188cdd"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3197061,
					"digest": "sha256:fbb9179cbbb1aa191e9ec19e77b7e6226b97ba8ac9d382822970ddcc61ce05e2"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2661004,
					"digest": "sha256:38876f988da32a7ef093d5245f6923bb9b748362834345a3e8995f72ef49e168"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2547018,
					"digest": "sha256:b847bb2fe3fdc6bd69cd7d690bcef5a1399310ce17bea49bbd1d5fd3641d0140"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3291941,
					"digest": "sha256:78b11fcb7ca30ba09defc8dcfc1298ea20b970430d9836b4bc7c404840b49a03"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3255182,
					"digest": "sha256:18ee4cd834fc95727859c15eaedc1f5670c4e9f3f74e3f45a108d5b9167344ab"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1576728,
					"digest": "sha256:ad443f24b1ed77880e534a0c1aca0ca5a722e866b7611a85e9fd7ef3463dea08"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1511595,
					"digest": "sha256:a6cd17527f03923a723839bdbe57ec200a008de6c8b35c0c4d900a675adac014"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2689335,
					"digest": "sha256:cd146e648d04aaf6161526eb8be96d3bf9c8fc22e433c4e6834dc972b4f09830"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3478686,
					"digest": "sha256:bf32d3cbc5fb2d1c54c54f964b11e10d70c58f340dc17857d45db246807bcca6"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2639800,
					"digest": "sha256:c505716a4281aec3fc577e417a29d23a19d1ff720f498aef49b9ed362ab53d88"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3382413,
					"digest": "sha256:17c8e16edff8afd8ef3b3b1d1d061351245287c59c6ab8846c1af94b414e1865"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 555,
					"digest": "sha256:57bf259ba83920a4e2575582423c084670209a37fdb503fbbc7e10d75a5419d0"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2798929,
					"digest": "sha256:e71af6bec187b3f6627f52f6f2808c6b946b5cc09b7f4a6ee006cc5557254ebc"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1678930,
					"digest": "sha256:fcf29a1baad8cb3d657d685b47ab630602a5c47b333f7ed9d7cbcae68a300801"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1826608,
					"digest": "sha256:fad10dc586613d133e71639507d157251d8d533d9c94c3897ed5ae6c557bcdf7"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2637303,
					"digest": "sha256:bc22ffea56cce18044465269f6958bfb8ef37e182c82c6ab9f2821525c034717"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1623998,
					"digest": "sha256:63fd16f5b2cc4af356f18b61281870994919fcb17f3a51f490fc09b6cd5e0cfe"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3091878,
					"digest": "sha256:71dea8ea27aab08e92be2cb1c61ba12693f8dc622575cd9087e689fdabd048fd"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 8496432,
					"digest": "sha256:547d0874d2b5a13d766fc98a54725cce8b5ec50af624d5c21b971ca0e668d7e0"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2644647,
					"digest": "sha256:27bd211db33f03f1ad558807c8312a3bf537e154b591f7067a7eab8d9364f10b"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 5318,
					"digest": "sha256:3455451f8d3fd909057c5f94d9c34b5618fa6f62db51ed540cd7931c146a5fd6"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3472177,
					"digest": "sha256:a77b05080df7a983ee586813839673e2dfdb852c87589ddf0900517bc928b779"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 8659224,
					"digest": "sha256:6a58fb7cc75d73d08a6d85d995ad767e5693c4afd8b4f94f89f61018a3925c67"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 5136,
					"digest": "sha256:437d9ac90cf3a6df605fbc9b691455242c3f4d7a3ba89b683daa5885314f1909"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 4385434,
					"digest": "sha256:52814f0ca0c9d85074d817b558ccb3583cdb394d1ec02cf3e45e860b24ef3f7b"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 1838119,
					"digest": "sha256:7e8facf10ccafea1b9688a4a4ba424d36f92e3e4cea1e913577bb45eb5bdc5f9"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 2464280,
					"digest": "sha256:b8531cb5032999225be8589ab8bec5ea275fffb47353863c29d1afcdf7966104"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 3468583,
					"digest": "sha256:55e985d488da8faeaa9772b7cfb3073d458fa626c89a390b292ab879be30f157"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 214,
					"digest": "sha256:7d37e76963e86dbc76a9cbd3a6afa21c127bb790f77415b608da57daa9019f10"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 187,
					"digest": "sha256:3aaaa9c943efae7533b9a28a28a676354866cad58208bcd7988b055c754653f0"
				},
				{
					"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
					"size": 32,
					"digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"
				}
			]
		}
	}
]

validation

Create a Spring Boot 3 application with web and actuator which is enough for a simple test.

# Create a Spring Boot 3 application
curl https://start.spring.io/starter.tgz -d dependencies=web,actuator -d javaVersion=17 -d bootVersion=3.0.0-SNAPSHOT -d type=maven-project | tar -xzf -

I’m using SNAPSHOT, because YOLO, but RC2 is already available!

Add a profile to the pom.xml, that will use the multi-arch builder that we created above.

# Remove the last line of the pom.xml file (on Mac OS X)
sed -i '' -e '$ d' pom.xml
# Add the new profile to the end of the pom.xml
echo "
  <profiles>
    <profile>
      <id>dashaun</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
              <image>
                <builder>dashaun/java-native-builder-multiarch:7.37.0</builder>
              </image>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>
</project>" >> pom.xml 

Now your project will use the multi-architecture builder that created.

optional

To watch, and be amazed, by the correct images being pulled down to your machine, run these commands to remove any unused images from your machine.

docker kill $(docker ps -q)
docker system prune -a --volumes

build on AMD64 or ARM64

The magical moment! You can do this from AMD64 or ARM64.

./mvnw -Pnative,dashaun spring-boot:build-image

You should have an image created. One layer of that image, is a statically linked binary for your architecture.

inspect the new image.

# find the local image
docker images | grep demo
# Look at the architecture for the image
docker inspect demo:0.0.1-SNAPSHOT | jq '.[0].Architecture'

The | jq.[0].Architecture can be replaced with | grep Architecture if you don’t have jq installed

run and test the image

Run the OCI image with docker to start up the server. It should start quickly!

# Forward the port, run in the background, but see the startup time
docker run -p 8080:8080 demo:0.0.1-SNAPSHOT 
# Check the endpoint to validate
http :8080/actuator/health

Thanks

Its working

Finally

I want to hear from you! All of my social links are here at dashaun.com.

Issues and feedback can be left in the GitHub dashaun/java-native-builder-multiarch repository.