feat: arm64 xgoreleaser docker image - #24
Conversation
On arm64 build hosts, linux-cgo-amd64 and alpine-cgo-amd64 goreleaser builds failed because CC resolved to the native aarch64-gcc, which does not support the -m64 flag Go passes when targeting linux/amd64. - Add CC=x86_64-linux-gnu-gcc to linux-cgo-amd64 (native on amd64, cross-compiler on arm64 via gcc-x86-64-linux-gnu package) - Change CC=musl-gcc to CC=x86_64-linux-musl-gcc for alpine-cgo-amd64 to use a consistent, architecture-aware name - Add a --platform=linux/amd64 Docker stage to capture the x86_64 musl sysroot (startup files + libc) and copy it into the final image - On amd64: symlink x86_64-linux-musl-gcc -> musl-gcc (no change in behaviour) - On arm64: install gcc-x86-64-linux-gnu from apt and create a wrapper script x86_64-linux-musl-gcc that invokes it with -B/-L pointing at the musl sysroot Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous approach used FROM --platform=linux/amd64 to extract the x86_64 musl sysroot, but that fails on arm64 CI runners without QEMU (exec format error). Instead, download x86_64-linux-musl-cross.tgz from musl.cc and extract only the x86_64-linux-musl/ subdirectory (crt files, libc, headers). These are x86_64 target files — not host executables — so no QEMU is needed to extract or use them. The x86_64-linux-gnu-gcc cross-compiler from apt provides the actual compiler binary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The tarball contains hard links in x86_64-linux-musl/bin/ that point to files in bin/ (the cross-compiler executables). Extracting only the x86_64-linux-musl/ subtree fails because the hard-link targets are not present. Extract the whole tarball, move only the sysroot part, then delete the rest. All within a single RUN layer so the final image is not bloated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ive CI runners Install libc6-dev-amd64-cross on arm64 to provide x86_64 glibc headers (bits/libc-header-start.h) needed by x86_64-linux-gnu-gcc cross-compiler. Split docker-publish workflow into per-arch native builds to avoid QEMU emulation, then combine with a manifest job. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adding /aarch64-linux-musl-cross/bin to PATH exposes unprefixed `ld` which shadows the system linker. When aarch64-linux-gnu-gcc invokes the linker for glibc builds, it finds the musl ld which doesn't support dynamic loading (needed for LTO plugins). Symlink only the prefixed tools (aarch64-linux-musl-*, arm-linux-musleabihf-*) into /usr/local/bin so goreleaser finds them without PATH conflicts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… arm64 hosts On arm64 build hosts, the arm-linux-musleabihf-cross tarball from musl.cc contains x86_64-hosted compiler binaries that cannot run natively. Extract just the musl sysroot and wrap the already-installed gcc-arm-linux-gnueabihf (Debian package) using the same -B/-L/-isystem pattern as the x86_64 musl wrapper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds arm64 support for the oryd/xgoreleaser Docker image by making the image build architecture-aware, updating the embedded toolchains, and extending CI to build/test/publish for both amd64 and arm64.
Changes:
- Update the Dockerfile to download the correct GoReleaser binaries per target architecture and install arch-specific musl toolchains.
- Adjust
build.tmpl.ymlto use explicitly prefixed compilers for amd64 (glibc + musl) builds. - Update GitHub Actions workflows to build/test on amd64 + arm64 and publish multi-arch images via a manifest job.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
build.tmpl.yml |
Switches amd64 CC values to explicit, prefixed toolchain binaries. |
Dockerfile |
Makes GoReleaser/toolchain setup depend on TARGETARCH and adds arm64-specific musl sysroot/compiler setup. |
.github/workflows/docker-test.yml |
Runs PR build/test on both amd64 and arm64 runners. |
.github/workflows/docker-publish.yml |
Builds/pushes per-arch images and creates/pushes multi-arch manifests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| docker buildx create --use | ||
| go_version="${{ github.event.inputs.golang }}" | ||
| goreleaser_version="${{ github.event.inputs.goreleaser }}" | ||
| arch="${{ matrix.platform##*/}}" |
There was a problem hiding this comment.
arch="${{ matrix.platform##*/}}" is not valid GitHub Actions expression syntax (the ##*/ parameter expansion is Bash-only). This will fail workflow parsing or produce an empty value, resulting in incorrect tags. Compute arch in Bash from ${{ matrix.platform }} (e.g., assign the platform string first, then use shell parameter expansion), or define arch as a separate matrix field.
| arch="${{ matrix.platform##*/}}" | |
| platform="${{ matrix.platform }}" | |
| arch="${platform##*/}" |
| RUN case "${TARGETARCH}" in \ | ||
| amd64) GR_ARCH=x86_64 ;; \ | ||
| arm64) GR_ARCH=arm64 ;; \ | ||
| *) echo "Unsupported TARGETARCH: ${TARGETARCH}"; exit 1 ;; \ | ||
| esac \ | ||
| && curl -LO https://github.com/goreleaser/goreleaser/releases/download/v${GORELEASER_VERSION}/goreleaser_Linux_${GR_ARCH}.tar.gz \ | ||
| && mkdir -p goreleaser_Linux_${GR_ARCH} \ | ||
| && tar -xvf goreleaser_Linux_${GR_ARCH}.tar.gz -C goreleaser_Linux_${GR_ARCH} \ | ||
| && mv goreleaser_Linux_${GR_ARCH}/goreleaser /usr/local/bin/goreleaser-oss \ | ||
| && rm -rf goreleaser_Linux_${GR_ARCH}.* goreleaser_Linux_${GR_ARCH}/ | ||
|
|
||
| RUN case "${TARGETARCH}" in \ | ||
| amd64) GR_ARCH=x86_64 ;; \ | ||
| arm64) GR_ARCH=arm64 ;; \ | ||
| *) echo "Unsupported TARGETARCH: ${TARGETARCH}"; exit 1 ;; \ | ||
| esac \ |
There was a problem hiding this comment.
The Dockerfile now hard-fails if TARGETARCH is unset. TARGETARCH is only guaranteed when building with BuildKit/buildx; if someone builds with the legacy builder (or with BuildKit disabled), ${TARGETARCH} will be empty and the case will exit 1. Consider adding a robust fallback (e.g., derive the arch from dpkg --print-architecture/uname -m) or defaulting safely so local docker build keeps working.
| RUN case "${TARGETARCH}" in \ | |
| amd64) GR_ARCH=x86_64 ;; \ | |
| arm64) GR_ARCH=arm64 ;; \ | |
| *) echo "Unsupported TARGETARCH: ${TARGETARCH}"; exit 1 ;; \ | |
| esac \ | |
| && curl -LO https://github.com/goreleaser/goreleaser/releases/download/v${GORELEASER_VERSION}/goreleaser_Linux_${GR_ARCH}.tar.gz \ | |
| && mkdir -p goreleaser_Linux_${GR_ARCH} \ | |
| && tar -xvf goreleaser_Linux_${GR_ARCH}.tar.gz -C goreleaser_Linux_${GR_ARCH} \ | |
| && mv goreleaser_Linux_${GR_ARCH}/goreleaser /usr/local/bin/goreleaser-oss \ | |
| && rm -rf goreleaser_Linux_${GR_ARCH}.* goreleaser_Linux_${GR_ARCH}/ | |
| RUN case "${TARGETARCH}" in \ | |
| amd64) GR_ARCH=x86_64 ;; \ | |
| arm64) GR_ARCH=arm64 ;; \ | |
| *) echo "Unsupported TARGETARCH: ${TARGETARCH}"; exit 1 ;; \ | |
| esac \ | |
| RUN ARCH="${TARGETARCH}" \ | |
| && if [ -z "${ARCH}" ]; then \ | |
| if command -v dpkg >/dev/null 2>&1; then \ | |
| ARCH="$(dpkg --print-architecture)"; \ | |
| else \ | |
| ARCH="$(uname -m)"; \ | |
| fi; \ | |
| fi \ | |
| && case "${ARCH}" in \ | |
| amd64|x86_64) GR_ARCH=x86_64 ;; \ | |
| arm64|aarch64) GR_ARCH=arm64 ;; \ | |
| *) echo "Unsupported architecture: ${ARCH}"; exit 1 ;; \ | |
| esac \ | |
| && curl -LO https://github.com/goreleaser/goreleaser/releases/download/v${GORELEASER_VERSION}/goreleaser_Linux_${GR_ARCH}.tar.gz \ | |
| && mkdir -p goreleaser_Linux_${GR_ARCH} \ | |
| && tar -xvf goreleaser_Linux_${GR_ARCH}.tar.gz -C goreleaser_Linux_${GR_ARCH} \ | |
| && mv goreleaser_Linux_${GR_ARCH}/goreleaser /usr/local/bin/goreleaser-oss \ | |
| && rm -rf goreleaser_Linux_${GR_ARCH}.* goreleaser_Linux_${GR_ARCH}/ | |
| RUN ARCH="${TARGETARCH}" \ | |
| && if [ -z "${ARCH}" ]; then \ | |
| if command -v dpkg >/dev/null 2>&1; then \ | |
| ARCH="$(dpkg --print-architecture)"; \ | |
| else \ | |
| ARCH="$(uname -m)"; \ | |
| fi; \ | |
| fi \ | |
| && case "${ARCH}" in \ | |
| amd64|x86_64) GR_ARCH=x86_64 ;; \ | |
| arm64|aarch64) GR_ARCH=arm64 ;; \ | |
| *) echo "Unsupported architecture: ${ARCH}"; exit 1 ;; \ | |
| esac \ |
| RUN case "${TARGETARCH}" in \ | ||
| amd64) GR_ARCH=x86_64 ;; \ | ||
| arm64) GR_ARCH=arm64 ;; \ | ||
| *) echo "Unsupported TARGETARCH: ${TARGETARCH}"; exit 1 ;; \ | ||
| esac \ | ||
| && curl -LO https://github.com/goreleaser/goreleaser/releases/download/v${GORELEASER_VERSION}/goreleaser_Linux_${GR_ARCH}.tar.gz \ | ||
| && mkdir -p goreleaser_Linux_${GR_ARCH} \ | ||
| && tar -xvf goreleaser_Linux_${GR_ARCH}.tar.gz -C goreleaser_Linux_${GR_ARCH} \ | ||
| && mv goreleaser_Linux_${GR_ARCH}/goreleaser /usr/local/bin/goreleaser-oss \ | ||
| && rm -rf goreleaser_Linux_${GR_ARCH}.* goreleaser_Linux_${GR_ARCH}/ | ||
|
|
||
| RUN case "${TARGETARCH}" in \ | ||
| amd64) GR_ARCH=x86_64 ;; \ | ||
| arm64) GR_ARCH=arm64 ;; \ | ||
| *) echo "Unsupported TARGETARCH: ${TARGETARCH}"; exit 1 ;; \ | ||
| esac \ | ||
| && curl -Lo "goreleaser-pro_Linux_${GR_ARCH}.tar.gz" "https://github.com/goreleaser/goreleaser-pro/releases/download/v${GORELEASER_VERSION}/goreleaser-pro_Linux_${GR_ARCH}.tar.gz" \ | ||
| && mkdir -p goreleaser-pro_Linux_${GR_ARCH} \ | ||
| && tar -xvf goreleaser-pro_Linux_${GR_ARCH}.tar.gz -C goreleaser-pro_Linux_${GR_ARCH} \ | ||
| && mv goreleaser-pro_Linux_${GR_ARCH}/goreleaser /usr/local/bin/goreleaser \ | ||
| && rm -rf goreleaser-pro_Linux_${GR_ARCH}.* goreleaser-pro_Linux_${GR_ARCH}/ |
There was a problem hiding this comment.
The curl commands here download goreleaser binaries from GitHub and immediately extract and install them without any checksum or signature verification. If an attacker compromises DNS/TLS, the release assets, or the GitHub repository, they could serve a malicious binary that runs in the build environment and compromises all produced artifacts. Add integrity verification (e.g., pinned checksums or GPG signature verification) for the goreleaser archives before extraction, or vendor the binaries, to ensure only trusted code is executed.
No description provided.