Skip to content

Fix armhf image build: stop pinning BUILDPLATFORM to linux/amd64 - #3453

Open
axl89 wants to merge 1 commit into
passteque:masterfrom
axl89:fix/armhf-build
Open

Fix armhf image build: stop pinning BUILDPLATFORM to linux/amd64#3453
axl89 wants to merge 1 commit into
passteque:masterfrom
axl89:fix/armhf-build

Conversation

@axl89

@axl89 axl89 commented Sep 1, 2026

Copy link
Copy Markdown

The problem

Building the image natively on a 32-bit ARM host fails immediately:

 => ERROR [base  3/10] RUN apk --update add git g++ findutils
1.271 exec /bin/sh: exec format error

The cause

The toolchain stages are pinned to the build platform, which is correct:

FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base

But the Dockerfile also declared that built-in argument with a default:

ARG BUILDPLATFORM=linux/amd64

A declared default takes precedence over the value BuildKit injects, so every
FROM --platform=${BUILDPLATFORM} stage was pinned to linux/amd64 regardless of where the
build actually ran. On an armhf host that pulls the amd64 golang image, and the first RUN
inside it dies with exec format error.

This is invisible in CI, because CI builds on amd64 and the hardcoded value happens to match
the real build platform there. It only bites people building natively on arm.

Minimal reproduction on an amd64 host, which fails in the same way with the platforms mirrored:

ARG BUILDPLATFORM=linux/arm/v7
FROM --platform=${BUILDPLATFORM} alpine:3.21
RUN echo hello

The change

  • Remove the ARG BUILDPLATFORM=linux/amd64 declaration, restoring the built-in value, and
    leave a comment so it does not come back. This is the only functional change.
  • Add build-armhf.sh, which creates a docker-container driver builder (the default docker
    driver cannot build for another platform), passes VERSION/COMMIT/CREATED, and checks for
    the QEMU binfmt handler up front rather than failing halfway through the build. PLATFORM,
    TAG and PUSH are configurable.
  • Add doc/build-armhf.md covering the diagnosis, the linux/arm/v7 versus linux/arm/v6
    naming confusion, and the QEMU setup needed for cross builds.

Nothing else about the build changes: all the auxiliary images (xcputranslate, both binpot
images, golang-alpine and alpine) already publish linux/arm/v6 and linux/arm/v7
manifests, so a native armhf build has everything it needs.

Verification

On an amd64 host, docker buildx build --platform=linux/arm/v7 --target=build now completes —
[base 3/10] passes and the resulting binary is ELF 32-bit LSB executable, ARM, EABI5.

The final alpine stage runs apk add on the target architecture, so whilst cross building it
still needs the QEMU binfmt handlers (docker run --privileged --rm tonistiigi/binfmt --install arm),
exactly as CI's docker/setup-qemu-action provides. That stage is untouched by this PR, and a
native build on the ARM host needs no emulation at all.

`ARG BUILDPLATFORM=linux/amd64` overrode the value BuildKit injects, so every
`FROM --platform=${BUILDPLATFORM}` stage was pinned to amd64 no matter where the
build ran. Building natively on an armhf host pulled the amd64 golang image and
died on the first RUN of the base stage:

    => ERROR [base  3/10] RUN apk --update add git g++ findutils
    exec /bin/sh: exec format error

Removing the declaration restores the built-in value. Also adds build-armhf.sh
(checks for the QEMU binfmt handler needed when cross building, creates a
container-driver builder, passes VERSION/COMMIT/CREATED) and doc/build-armhf.md.

Verified: `docker buildx build --platform=linux/arm/v7 --target=build` completes
on an amd64 host and produces an "ELF 32-bit LSB executable, ARM, EABI5" binary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant