When linking an executable with -static flag, starting with GCC 15 (as available in Alpine Linux 3.23+) implies the flag -pie.
PIE stands for Position-independent executable. That’s generally a good idea as it enables address space layout randomization for improved security.
The resulting binary is still statically linked (file reports it as static-pie linked) and doesn’t load any libraries at runtime. But due to PIE it apparently cannot start itself and needs ld-musl as entrypoint.
Many people use Alpine Linux to produce portable binaries without any runtime dependencies. PIE binaries are incompatible for that due to their ld dependency.
I understand the same as you, that a static build means no dependencies for real portability, which means no PIE (-no-pie by default).
And for the cases in which PIE is required, using an opt-in flag should be good, like:
crystal build --static --link-flags="-static-pie"
or --link-flags="-pie".
–
I know that this is not universal and, for example, OpenBSD does static PIE by default for ASLR.
But, in vanilla GCC, -static has always meant: “On systems that support dynamic linking, this overrides -pie and prevents linking with the shared libraries.”
The issue is with Alpine’s GCC, built with --enable-default-pie. So both cc -static and cc -static-pie will produce a static PIE.
Returning to not adding PIE by default follows vanilla GCC behavior.
Pin --static to non-PIE (what the post suggests) — restores portability, matches Go’s default for static binaries, works with older GCC.
Make --static produce interpreter-free static-PIE (the Rust approach) — portability and ASLR, but requires driving the linker’s crt selection rather than relying on the driver default.
Either way, the compiler pinning the behavior explicitly seems better than inheriting whatever the driver does, since that’s what silently changed.
Just a question: does the portable-compiler binary need the same treatment, and is there a preferred direction between the two?
However, the above issue made me realize that static-pie executables are actually portable.
Alpine’s ldd reports a dependency on ld-musl but that doesn’t seem to be actually fixed in the executable. It runs fine on a glibc system.
Debian’s ldd still reports it as statically linked, without any dynamic dependency.
So part of my initial assessment was apparently wrong, and I should’ve properly verified the actual behaviour.
The original failure (GCC 15 on Alpine Linux changes `-static` to imply `-pie` (i.e. `-static-pie)` · Issue #441 · crystal-lang/distribution-scripts · GitHub) is actually just a broken test. The actual file still works.
We use ldd to detect whether the executable is statically linked, and the reporting for static-pie linked executables is different from what we expect.
We probably don’t need to build with -no-pie, we just need to fix the test.