# manylinux

manylinux is a convention plus a set of prebuilt Docker images for producing binary Python extension wheels that install and run across most Linux distributions. A wheel with C or Rust extensions is compiled against a specific glibc and a specific set of system libraries; build it naively on your dev machine and it may refuse to load on someone else's. manylinux exists so that "one wheel, most Linuxes" is achievable, and it's really a series of PEPs defining what "most Linuxes" means, with images that let you build against that baseline.

## How it works

The core insight is that a binary built on one distribution runs on distributions the same age or newer, but not older — a wheel linked against a new glibc won't load where glibc is older. So manylinux builds on deliberately *old* base images, and the resulting wheel works on everything from that vintage forward. The platform tag on the wheel encodes exactly which baseline it targets, and the installer (pip) checks the tag against the host before installing.

The platform-tag lineage is a chain of PEPs:

- **PEP 513** — manylinux1, based on CentOS 5
- **PEP 571** — manylinux2010, CentOS 6
- **PEP 599** — manylinux2014, CentOS 7
- **PEP 600** — the future-proof `manylinux_x_y` scheme, keyed directly to a glibc version (e.g. `manylinux_2_28` means glibc 2.28) instead of minting a new named tag per distro generation
- **PEP 656** — `musllinux_x_y`, the same idea keyed to musl for Alpine and other musl systems

Images live at `quay.io/pypa/...` and are tagged so a build can pin an exact image for repeatable results. Current images include manylinux2014, `manylinux_2_28` (AlmaLinux 8), `manylinux_2_34` (AlmaLinux 9, alpha), `manylinux_2_39` (AlmaLinux/Rocky 10, alpha, and the first to add riscv64), `manylinux_2_31` and `manylinux_2_35` (armv7l), and `musllinux_1_2` (Alpine 3.22). Architectures covered across the set: x86_64, i686, aarch64, ppc64le, s390x, armv7l, riscv64. The RHEL-derivative images use yum/dnf; the armv7l images are Debian-based and use apt instead.

## Usage

```bash
docker run --rm -v $(pwd):/io quay.io/pypa/manylinux_2_28_x86_64 \
  /io/build-wheels.sh
# inside: build with an old-glibc toolchain, then run auditwheel repair
auditwheel repair dist/mypkg-*.whl -w /io/wheelhouse/
```

`auditwheel` is the companion tool: it inspects a built wheel, confirms it only depends on libraries in the manylinux baseline, and grafts any extra shared libraries into the wheel so it's self-contained.

## Limitations

- The biggest sharp edge: RHEL 9 and its derivatives target the `x86-64-v2` micro-architecture. Libraries pulled in via `dnf` on a `manylinux_2_28`+ image can be `v2`-compiled and grafted into a wheel, which then fails on older x86_64 hardware that predates `v2`. No PEP handles micro-architecture variants, and auditwheel does not detect this — so a wheel can pass every check and still crash on an older CPU.
- Old-baseline images mean an old toolchain. Building against manylinux2014 (CentOS 7) gives you a compiler and system libraries from that era unless you install newer ones yourself.
- Alpha images (`manylinux_2_34`, `_2_39`) aren't stable targets yet.

This is the wheel-packaging face of [[abi-stability|ABI stability]]: manylinux is Python's answer to glibc's habit of breaking binaries, achieved by building old and bundling everything, much like [[dependency-vendoring|vendoring]] ships self-contained artifacts rather than trusting the host. Compare [[win32-stable-abi]] for the argument that the C library is the unstable layer on Linux, and [[supply-chain-security]] for why grafting third-party libraries into a wheel is also a trust-boundary question.

## Repo

[github.com/pypa/manylinux](https://github.com/pypa/manylinux) — Shell, MIT, ~1.76k stars.
