Usually, when you need build a bootable Spring Boot image, bootBuildImage is exactly what you’re looking for. The plugin has got many capabilities, including pushes to docker image registries.

Unfortunately, Spring Boot does not have a native support for multi-architecture builds (or even cross-architecture builds for that matter), which was a real blocker for me since most of my hobby development is done on an AMD64 processor but I deploy most of the images on my Raspberry Pi rack which uses ARM64.

I already knew that docker does support multi-architecture builds, so I was a stone’s throw away from a script I could run from gradle.

I created a new task buildMultiArchImage which exactly does this. Since I don’t use gitlab pipelines for my personal projects (I couldn’t be bothered), I run DOCKER_HUB_USERNAME=... DOCKER_HUB_PASSWORD=... ./gradlew buildMultiArchImage directly from my machine.

Here is what buildMultiArchImage looks like:


val bootBuildImageTaskName = "buildMultiArchImage"

/**
 * At the time of writing, there was no easy way of building multiplatform
 * Docker, nor there was any reasonable Docker replacement which would have
 * this feature.
 *
 * If possible, please use a more endorsed means of OCI image building!
 */
val bootBuildImage = tasks.register(bootBuildImageTaskName) {
    group = "build"
    description = "Builds multi-architecture Docker images using 'docker buildx'."
    mustRunAfter("build")

    doLast {
        val usernameEnv = "DOCKER_HUB_USERNAME"
        val passEnv = "DOCKER_HUB_PASSWORD"
        val platforms = "linux/amd64,linux/arm64"

        val user = requireEnvironmentVariable(usernameEnv)
        val pass = requireEnvironmentVariable(passEnv)
        val dockerImageRepositoryBaseUrl = System.getenv("DOCKER_HUB_URL") ?: "docker.io"

        exec {
            commandLine("docker", "buildx", "create", "--use")
            commandLine("docker", "login", "-u", user, "-p", pass)
        }
        exec {
            buildAndPushMultiArchImage(
                platforms = platforms,
                dockerImageRepositoryBaseUrl = dockerImageRepositoryBaseUrl,
                user = user,
                imageName = "beehive-external",
                dockerfileName = "main.Dockerfile"
            )
        }
        // you can build and push as many images as you need, just continue with a new `exec` block and buildAndPushMultiArchImage
    }
}

private fun ExecSpec.buildAndPushMultiArchImage(
    platforms: String,
    dockerImageRepositoryBaseUrl: String,
    user: String,
    imageName: String,
    dockerfileName: String
) {
    commandLine(
        "docker", "buildx", "build",
            "--platform", platforms,
            "-t", "${dockerImageRepositoryBaseUrl}/${user}/${imageName}:${project.version}",
            "--push", rootDir.absolutePath,
            "-f", listOf(rootDir.absolutePath, dockerfileName).joinToString(File.separator)
    )
}

private fun requireEnvironmentVariable(passEnv: String) = requireNotNull(System.getenv(passEnv)) {
    "When using the task '$bootBuildImageTaskName', you must specify a dockerhub username via env property $passEnv ."
}