Alpine "not found" for a binary that's right there: glibc vs musl
A binary exists on Alpine, yet running it says "not found" or "no such file or directory": it was built for glibc. Check with ldd, then rebuild it or use gcompat.
by OhSudo ··
Checked on real software: Alpine 3.23, Docker · November 2026How we check
Not sure this is your cause?Find it step by step: Container won't start →
Part of a learning path:Docker: from first container to production
It’s one of the most confusing errors you can get. The file is right there, and it’s executable:
/ # ls -l /app/server-rwxr-xr-x 1 root root 16040 Nov 6 09:12 /app/server/ # /app/server/bin/sh: /app/server: not foundIn Docker, it can look like this:
exec /app/server: no such file or directoryThe file you’re looking at isn’t what’s missing. What’s missing is the program that has to load it. A binary built on Debian, Ubuntu or RHEL expects the glibc loader, at a path like /lib64/ld-linux-x86-64.so.2. Alpine uses musl instead, and that path doesn’t exist. The kernel reports the missing loader as “not found”, and doesn’t say which file it means.
- A binary built on Debian, Ubuntu or RHELneeds /lib64/ld-linux-x86-64.so.2run on(fails)
- Alpinehas musl, not glibc(breaks here)so
- "not found"the kernel reports the missing loader
TL;DR
file /app/server # is it glibc? (apk add file)ldd /app/server # what can't be loaded?Then choose one: build it on Alpine, build it static, use a Debian-slim image, or, for simple programs, apk add gcompat.
1. Confirm it
apk add --no-cache filefile /app/server/app/server: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, .../lib64/ld-linux-x86-64.so.2 is the glibc loader. A program built for Alpine shows /lib/ld-musl-x86_64.so.1. Then see what else it wants:
ldd /app/serverError loading shared library libstdc++.so.6: No such file or directory (needed by /app/server)Error relocating /app/server: __printf_chk: symbol not foundAlso rule out the two look-alikes. A wrong architecture gives exec format error: see that article. A script with Windows line endings, #!/bin/sh\r, gives no such file or directory too, as the interpreter’s name then has an invisible character at the end. file says with CRLF line terminators.
2. Build it on Alpine
The cleanest fix is to compile in the same kind of image you run, usually with a multi-stage build:
FROM alpine:3.23 AS buildRUN apk add --no-cache build-baseCOPY . /srcRUN cc -O2 -o /app /src/main.c
FROM alpine:3.23RUN apk add --no-cache libstdc++COPY --from=build /app /usr/local/bin/appA C++ program needs libstdc++ at run time too.
3. Go: build it static
A Go program uses glibc only when cgo is on, and it’s on by default when a C compiler is around, as on golang:1.x images. Turn it off, and you get a binary that runs anywhere:
FROM golang:1.25 AS buildWORKDIR /srcCOPY . .RUN CGO_ENABLED=0 go build -o /app .
FROM alpine:3.23COPY --from=build /app /appENTRYPOINT ["/app"]If you have to keep cgo, such as for SQLite, build in golang:1.25-alpine so that it links against musl. The Dockerfile Generator builds Go with cgo turned off.
4. Node.js and Python: native modules
Packages with compiled parts, such as bcrypt, sharp, numpy or psycopg2-binary, are the usual way this reaches you:
Error: Error loading shared library ld-linux-x86-64.so.2: No such file or directoryDon’t copy node_modules or a virtualenv from your machine into the image. They contain binaries for your system. Install inside the image, and keep them out with .dockerignore:
FROM node:24-alpineWORKDIR /appCOPY package*.json ./RUN npm ci --omit=devCOPY . .With Python, pip needs musllinux wheels. Packages that don’t have them compile from source on Alpine, slowly, and need build-base and headers. That’s often the point where Alpine stops being worth it.
5. gcompat: for simple programs
Alpine has a compatibility layer for glibc programs:
apk add --no-cache gcompat/app/serverThat’s fine for a simple program you can’t rebuild. It doesn’t cover everything, so test it properly. A program can run and still crash on some call that isn’t implemented.
6. Or use another base image
Vendor binaries that only support glibc, or a stack full of native modules, are a sign to switch. Debian slim isn’t much bigger once your dependencies are in:
FROM debian:13-slim-slim tags of node, python and others are made for this. For something smaller, with glibc, look at gcr.io/distroless/*.
Musl behaves differently from glibc in other ways, too. Its DNS resolver has handled search domains and TCP differently, and it has a smaller default thread stack. So if something works only “almost” right on Alpine, this may be why.
Warning
Avoid installing a glibc package by hand on Alpine, or creating a link from /lib64/ld-linux-x86-64.so.2 to the musl loader. You can see both on forums. A link gets the program started, and it then crashes in unpredictable ways, as the two libraries aren’t the same.
Shell, we have a problem!
Similar, but different: error while loading shared libraries is a loader that runs and can’t find a library: see that article. exec format error means another CPU architecture: see that article. sh: bash: not found is the shell itself: Alpine has no bash unless you add it, so use #!/bin/sh.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.