From 1563dd355aeb21ef9bee12e9e56ee352ab08a265 Mon Sep 17 00:00:00 2001 From: "muralidhar.reddy" Date: Thu, 9 Jul 2026 15:18:51 +0530 Subject: [PATCH] fix(docker): revert builder base from rust:alpine to rust:latest Every build failure since switching the builder to rust:alpine (in an earlier "lighter/faster pull" change) traces back to one root cause, confirmed via web research: rust-lang/cargo#7563. *-unknown-linux-musl targets default to +crt-static, which is fundamentally incompatible with proc-macro crates (dylib-style loading) on a musl HOST. sqlx-macros needs to run natively during compilation regardless of the cross --target requested for the final binary, and on an Alpine/musl builder that host-native build is broken by this cargo/rustc limitation -- no combination of CC/linker wrapper juggling fixes it, since the problem is the host toolchain itself, not our zig configuration. On a glibc host (rust:latest), the host triple (x86_64-unknown-linux-gnu) never touches musl or our zig wrapper at all -- proc-macros just build natively against glibc, and only the actual --target musl artifacts (compiled via the zig wrapper) end up statically linked. This is what the Dockerfile did for weeks (PRs #1-8) before the alpine switch. Verified locally: full cold-cache build for amd64 completes cleanly (previously failed consistently on alpine), and `file` on the resulting binary confirms "statically linked" -- the shipped artifact is still a fully static musl binary either way, since the builder's own base image has no bearing on the --target-compiled output. Only the throwaway build-time toolchain container changes; nothing about the shipped image does. --- Dockerfile | 76 +++++++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Dockerfile b/Dockerfile index 867e9c9..420da4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,7 @@ -# syntax=docker/dockerfile:1 -FROM --platform=$BUILDPLATFORM rust:alpine AS chef +FROM --platform=$BUILDPLATFORM rust:latest AS chef # TARGETARCH: auto-set by buildkit/buildah from host arch unless --platform is passed. ARG TARGETARCH -RUN apk add --no-cache build-base bash pkgconf python3 py3-pip +RUN apt-get update && apt-get install -y pkg-config python3-pip && rm -rf /var/lib/apt/lists/* RUN pip install ziglang --break-system-packages RUN cargo install cargo-chef cargo-zigbuild # cook must use --zigbuild too, or its fingerprint won't match the final build and everything recompiles twice. @@ -18,44 +17,49 @@ RUN case "$TARGETARCH" in \ # which breaks zig's -target parser. Strip those flags via a wrapper so any # future C dependency's buildsystem links correctly against musl. # -# One wrapper PER triple (not a single script reading a shared /zig_target.txt): -# build scripts and proc-macro crates (e.g. sqlx-macros) always compile for the -# HOST triple (x86_64, since BUILDPLATFORM pins this stage to the native runner) -# regardless of the cross --target passed for the final binary. A single global -# target file can't distinguish "CC invoked because it's the host triple" from -# "CC invoked because it's the cross target" -- when cross-compiling to arm64 it -# wrongly compiled host-triple C code (e.g. libsqlite3-sys for sqlx-macros) for -# aarch64 too, which then failed to link into the host-native x86_64 proc-macro -# artifact ("file in wrong format"). -RUN <<'SETUP' -set -eu -for pair in "x86_64:x86_64-linux-musl" "aarch64:aarch64-linux-musl"; do - rt="${pair%%:*}"; zt="${pair##*:}" - for tool in cc:cc g++:c++; do - name="musl-${tool%%:*}-${rt}"; zigcmd="${tool##*:}" - cat > "/usr/local/bin/${name}" < /usr/local/bin/musl-cc <<'WRAP' #!/bin/bash +zt=$(cat /zig_target.txt) args=() skip=0 -for a in "\$@"; do - if [ "\$skip" = 1 ]; then skip=0; continue; fi - case "\$a" in +for a in "$@"; do + if [ "$skip" = 1 ]; then skip=0; continue; fi + case "$a" in --target=*) continue ;; -target) skip=1; continue ;; esac - args+=("\$a") + args+=("$a") done -exec python3 -m ziglang ${zigcmd} -target ${zt} "\${args[@]}" +exec python3 -m ziglang cc -target "$zt" "${args[@]}" WRAP - chmod +x "/usr/local/bin/${name}" - done +RUN cat > /usr/local/bin/musl-g++ <<'WRAP' +#!/bin/bash +zt=$(cat /zig_target.txt) +args=() +skip=0 +for a in "$@"; do + if [ "$skip" = 1 ]; then skip=0; continue; fi + case "$a" in + --target=*) continue ;; + -target) skip=1; continue ;; + esac + args+=("$a") done -SETUP -ENV CC_x86_64_unknown_linux_musl=musl-cc-x86_64 -ENV CXX_x86_64_unknown_linux_musl=musl-g++-x86_64 +exec python3 -m ziglang c++ -target "$zt" "${args[@]}" +WRAP +RUN chmod +x /usr/local/bin/musl-cc /usr/local/bin/musl-g++ +ENV CC_x86_64_unknown_linux_musl=musl-cc +ENV CXX_x86_64_unknown_linux_musl=musl-g++ ENV AR_x86_64_unknown_linux_musl=ar -ENV CC_aarch64_unknown_linux_musl=musl-cc-aarch64 -ENV CXX_aarch64_unknown_linux_musl=musl-g++-aarch64 +ENV CC_aarch64_unknown_linux_musl=musl-cc +ENV CXX_aarch64_unknown_linux_musl=musl-g++ ENV AR_aarch64_unknown_linux_musl=ar WORKDIR /app @@ -65,17 +69,13 @@ RUN cargo chef prepare --bin uc-server --recipe-path recipe.json FROM chef AS builder COPY --from=planner /app/recipe.json recipe.json -# Own target/ cache id (separate Cargo.lock from aispecs/operator); registry cache id is -# shared globally. The "-alpine" suffix ties this cache to the builder base image: compiled -# .rlib/.so artifacts are toolchain/ABI-specific, so reusing a cache built under a different -# base (e.g. glibc rust:latest) causes relocation failures like a missing __ubsan_handle_* -# symbol. Bump this suffix again if the builder base image changes. +# Own target/ cache id (separate Cargo.lock from aispecs/operator); registry cache id is shared globally. RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry \ - --mount=type=cache,target=/app/target,id=cargo-target-uc-alpine \ + --mount=type=cache,target=/app/target,id=cargo-target-uc \ cargo chef cook --zigbuild --profile docker --bin uc-server --target "$(cat /rust_target.txt)" --recipe-path recipe.json COPY . . RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry \ - --mount=type=cache,target=/app/target,id=cargo-target-uc-alpine \ + --mount=type=cache,target=/app/target,id=cargo-target-uc \ cargo zigbuild --profile docker --target "$(cat /rust_target.txt)" -p uc-server && \ cp "/app/target/$(cat /rust_target.txt)/docker/uc-server" /uc-server-bin