Home How use packer to build multiple arch docker images
Post
Cancel

How use packer to build multiple arch docker images

Introduction

In this post, we are going to user packer to build a docker image to multiple architectures.

Defining our sources

Given that we are going to be building for multiple architectures, we also need our sources to pull from all the different architecture.

In this example we will make use of alpine linux to build our images.

So let’s first define a common source for amd64 architecture.

1
2
3
4
5
source "docker" "amd64" {
    image = "docker.io/amd64/alpine"
    commit = true
    platform = "linux/amd64"
}

Notice the platform, this is the equivalent of docker –platform argument

Also notice the image source is docker.io/amd64/alpine, this is the amd64 version of alpine linux, as the base repository for different architectures for alpine are actually based on their architecture.

You can see them here: https://github.com/docker-library/official-images#architectures-other-than-amd64

Now let’s define our source for other architectures

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
source "docker" "arm64" {
    image = "docker.io/arm64v8/alpine"
    commit = true
    platform = "linux/arm64/v8"
}

source "docker" "arm32" {
    image = "docker.io/arm32v7/alpine"
    commit = true
    platform = "linux/arm/v7"
}

source "docker" "i386" {
    image = "docker.io/i386/alpine"
    commit = true
    platform = "linux/i386"
}

source "docker" "ppc64le" {
    image = "docker.io/ppc64le/alpine"
    commit = true
    platform = "linux/ppc64le"
}

Ok, so that’s all the sources we need, now let’s define our builders.

Defining our builders

1
2
3
4
5
6
7
8
9
build {
    sources = [
        "source.docker.amd64",
        "source.docker.arm64",
        "source.docker.arm32",
        "source.docker.i386",
        "source.docker.ppc64le",
    ]
}

If we now just run packer build by running the command

1
packer build .

We just end up pull and committing to our local docker registry

So, now let’s imagine we just want to build for amd64 and arm64, we can do that by just specifying the sources we want to use.

1
packer build -only=source.docker.amd64,source.docker.arm64 .

But, for now we are not using any provisioners, so let’s add some.

Let’s keep this post simple and just use a shell provisioner to just use uname so we can check that the output actually matches the platform we are building for.

1
2
3
4
5
    provisioner "shell" {
        inline = [
            "uname -a",
        ]
    }

Now, if we run packer build again, we will see that it will see a different output for each architecture.

1
2
3
4
5
6
7
8
9
=> docker.amd64: Creating a temporary directory for sharing data...
==> docker.arm32: Creating a temporary directory for sharing data...
...
    docker.amd64: Linux 4d28a12e536c 5.10.104-linuxkit #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022 x86_64 Linux
    docker.arm32: Linux 5d1adcf285da 5.10.104-linuxkit #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022 armv7l Linux
    docker.arm64: Linux f841cbd37ea4 5.10.104-linuxkit #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022 aarch64 Linux
    docker.ppc64le: Linux 3e1453ad893c 5.10.104-linuxkit #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022 ppc64le Linux
    docker.i386: Linux 53e80c0a41b8 5.10.104-linuxkit #1 SMP PREEMPT Thu Mar 17 17:05:54 UTC 2022 i686 Linux
...

We can now continue to add further provisioners to our build, as another example we could install curl to our containers, before committing them to an image.

Example

1
2
3
4
5
    provisioner "shell" {
        inline = [
            "apk add --no-cache curl",
        ]
    }

And the appropriate binary will be installed to the corresponding architecture (curl is not the best example) but since we are running inside the container before commiting it to an image, it will be installed to the correct architecture.

Post processing our images

We can now just add to the build a post processor to push our images to a registry.

1
2
3
4
    post-processor "docker-tag" {
        repository = "local/packerbuild"
        tag = "latest"
    }

And now we can just run packer build and it will build our images locally

If we change the tag to reference the source, we can tag our images with the architecture.

1
2
3
4
    post-processor "docker-tag" {
        repository = "local/packerbuild"
        tag = ["${source.name}"]
    }            

We can make use of the reference for the source to other mechanisms to control the follow for different architectures.

For simplicity we will not be exploring those in this tutorial.

Inspecting commited images

Given we are committing our containers as images, let’s check the history of the images to see the layers we are getting from the provisioner shell commands we are running.

1
docker history --no-trunc -H local/packerbuild:amd64

Should give us the following output

1
2
3
4
MAGE                                                                     CREATED              CREATED BY                                                                                          SIZE      COMMENT
sha256:f570fd07ba52f62446716bcb4456b630eea4f5ef7dc90b1c68e6a1e73366febe   About a minute ago                                                                                                       2.26MB
sha256:042a816809aac8d0f7d7cacac7965782ee2ecac3f21bcf9f24b1de1a7387b769   13 days ago          /bin/sh -c #(nop)  CMD ["/bin/sh"]                                                                  0B
<missing>                                                                 13 days ago          /bin/sh -c #(nop) ADD file:e4d600fc4c9c293efe360be7b30ee96579925d1b4634c94332e2ec73f7d8eca1 in /    7.05MB

We only have the command and a missing layer with the file which is the installation for curl binary.

Conclusion

In this tutorial we have seen how we can use packer to build multi-architecture images based on several build sources, and how we can use the source reference to control the flow of the build for different architectures.

As usual our code is available on github.

You can find it under the following link https://github.com/Ilhicas/packer-examples/tree/main/docker-multiarch

This post is licensed under CC BY 4.0 by the author.