5.1 KiB
Pack Binaries: Cross-Architecture Static Build
Date: 2026-03-27
Problem
The attune-core-timer-sensor native sensor binary failed to execute in Docker containers on Apple Silicon (arm64) Macs with the error:
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2
Two root causes:
-
Wrong build toolchain:
docker/Dockerfile.pack-binariesused a plaincargo buildwhich produced a dynamically-linked, host-architecture binary. On arm64 Docker hosts, this created an aarch64 binary linked against glibc. When the sensor container tried to execute it, the required dynamic linker (ld-linux-x86-64.so.2) was absent. This contrasted withdocker/Dockerfile.agent, which already usedcargo-zigbuild+ musl for fully static binaries. -
init-packs overwrote the static binary: Even after fixing the Dockerfile, the
init-packs.shscript didcp -rffrom./packs/(host bind mount) into the packs volume, overwriting the freshly-placed static binary frominit-pack-binarieswith the old dynamically-linked binary from the host'spacks/core/sensors/directory.
Changes
docker/Dockerfile.pack-binaries — Rewritten for static cross-compilation
- Added
RUST_TARGETbuild arg (default:x86_64-unknown-linux-musl) - Installed
musl-tools,ziglang, andcargo-zigbuild(matching agent Dockerfile pattern) - Replaced
cargo build --releasewithcargo zigbuild --release --target ${RUST_TARGET} - Added
cargo fetchdependency caching layer with proper workspace stubs (including sensoragent_main.rs) - Added
SQLX_OFFLINE=truefor compile-time query checking without a live database - Added strip-with-fallback for cross-arch scenarios
- Added Stage 3:
pack-binaries-init— busybox-based image for Docker Compose volume population (analogous toagent-init) - Updated cache ID to
target-pack-binaries-staticwithsharing=lockedfor zigbuild exclusivity
docker/init-packs.sh — Preserve static binaries during pack copy
- Before copying host pack files, detects ELF binaries already present in the target
sensors/directory using the 4-byte ELF magic number (\x7fELF=7f454c46) viaod(available in python:3.11-slim, unlikefile) - Backs up detected ELF binaries to a temp directory before the
cp -rfoverwrites them - Restores the backed-up static binaries after the copy completes
- Logs each preserved binary for visibility
docker-compose.yaml — Added init-pack-binaries service
- New
init-pack-binariesservice builds fromDockerfile.pack-binaries(target:pack-binaries-init) and copies the static binary into thepacks_datavolume - Accepts
PACK_BINARIES_RUST_TARGETenv var (default:x86_64-unknown-linux-musl) init-packsnow depends oninit-pack-binariesto ensure binaries are in the volume before pack files are copieddocker compose upnow automatically builds and deploys pack binaries — no manual script run required
docker/distributable/docker-compose.yaml — Same pattern for distributable
- Added
init-pack-binariesservice using pre-built registry image - Updated
init-packsdependencies
scripts/build-pack-binaries.sh — Updated for static builds
- Passes
RUST_TARGETbuild arg to Docker build - Accepts
RUST_TARGETenv var (default:x86_64-unknown-linux-musl) - Updated verification output to expect statically-linked binary
Makefile — New targets
PACK_BINARIES_RUST_TARGETvariable (default:x86_64-unknown-linux-musl)docker-build-pack-binaries— build for default architecturedocker-build-pack-binaries-arm64— build for aarch64docker-build-pack-binaries-all— build both architectures
.gitignore — Exclude compiled pack binary
- Added
packs/core/sensors/attune-core-timer-sensorto.gitignore - Removed the stale dynamically-linked binary from git tracking
Architecture
The fix follows the same proven pattern as Dockerfile.agent:
cargo-zigbuild + musl → statically-linked binary → zero runtime dependencies
Since the binary has no dynamic library dependencies (no glibc, no libssl, no dynamic linker), it runs on any Linux container of the matching CPU architecture, regardless of the base image (Debian, Alpine, scratch, etc.).
Init sequence
init-pack-binaries: Builds static musl binary → copies topacks_datavolume atcore/sensors/init-packs(depends oninit-pack-binaries): Copies host./packs/core/to volume → detects existing ELF binary → backs it up → copies host files → restores static binarysensor: Spawns the staticattune-core-timer-sensor→ works on any architecture
Usage
# Default (x86_64) — works on amd64 containers and arm64 via Rosetta
docker compose up -d
# For native arm64 containers
PACK_BINARIES_RUST_TARGET=aarch64-unknown-linux-musl docker compose up -d
# Standalone build
make docker-build-pack-binaries # amd64
make docker-build-pack-binaries-arm64 # arm64
make docker-build-pack-binaries-all # both
# Manual script
RUST_TARGET=aarch64-unknown-linux-musl ./scripts/build-pack-binaries.sh