manylinux rust/pyo3 build guide

building python wheels with rust extensions using pyo3 and maturin requires careful management of glibc compatibility to ensure wheels work across different linux distributions.

overview

key challenges when building rust python extensions:

  • rust 1.64+ requires glibc 2.17 minimum (manylinux2014)
  • modern ubuntu/debian systems have incompatible glibc versions
  • pypi only accepts manylinux-tagged wheels, not generic linux
  • native builds on ubuntu 25.04 (glibc 2.41) fail manylinux compliance

manylinux standards

manylinux defines maximum glibc versions and allowed external libraries for python wheel compatibility.

glibc version requirements

standardbase osglibc versionrelease yearrust 1.64+ compatiblestatus
manylinux1centos 52.52016obsolete
manylinux2010centos 62.122018eol
manylinux2014centos 72.172019eol (june 2024)
manylinux_2_24debian 92.242020active
manylinux_2_28almalinux 82.282021active
manylinux_2_31almalinux 92.312022active
manylinux_2_34almalinux 92.342023active

critical: rust 1.64+ (august 2022) requires glibc 2.17 minimum, making manylinux2014 the oldest supported standard.

platform compatibility

native build failures

modern linux distributions ship with glibc versions incompatible with manylinux requirements:

distributionglibc versionnative build result
ubuntu 20.04 lts2.31❌ fails compliance
ubuntu 22.04 lts2.35❌ fails compliance
ubuntu 24.04 lts2.39❌ fails compliance
ubuntu 25.04 (plucky puffin)2.41❌ fails compliance
debian 11 bullseye2.31❌ fails compliance
debian 12 bookworm2.36❌ fails compliance
debian 13 trixie2.41❌ fails compliance
rhel/almalinux 82.28⚠️ manylinux_2_28 only
rhel/almalinux 92.34⚠️ manylinux_2_34 only

example error on ubuntu 25.04

$ maturin build --release
💥 maturin failed
  Caused by: Error ensuring manylinux_2_17 compliance
  Caused by: Your library is not manylinux_2_17 (aka manylinux2014) compliant
  because of the presence of too-recent versioned symbols:
  ["libc.so.6 offending versions: GLIBC_2.18, GLIBC_2.25, GLIBC_2.28, GLIBC_2.29, GLIBC_2.33, GLIBC_2.34",
   "libm.so.6 offending versions: GLIBC_2.29"]

build strategies

the official pyo3/maturin docker image provides a controlled build environment with correct glibc versions.

# build manylinux-compliant wheels using docker
docker run --rm \
    -v "$(pwd)":/io \
    -w /io \
    ghcr.io/pyo3/maturin:latest \
    build --release \
    --strip \
    --manylinux 2_28 \
    -o dist

# for specific python versions
docker run --rm \
    -v "$(pwd)":/io \
    -w /io \
    ghcr.io/pyo3/maturin:latest \
    build --release \
    --strip \
    --manylinux 2_28 \
    --interpreter python3.11 python3.12 \
    -o dist

advantages:

  • guaranteed manylinux compliance
  • consistent build environment across systems
  • no local setup required

disadvantages:

  • requires docker installation
  • slower than native builds
  • may require additional configuration for cross-compilation

zig cross-compilation

use zig as a cross-compiler to target different glibc versions from any host:

# install zig support
pip install maturin[zig]

# build with zig targeting manylinux2014
maturin build --release --zig --manylinux 2_17

# target specific glibc version
maturin build --release --zig --manylinux 2_28

advantages:

  • build on any host os including macos
  • target arbitrary glibc versions
  • no docker required

disadvantages:

  • experimental feature
  • may have edge cases with complex dependencies
  • requires zig installation

github actions ci/cd

automate builds using pyo3/maturin-action:

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  linux:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        target: [x86_64, aarch64]
    steps:
      - uses: actions/checkout@v4
      - uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          manylinux: auto # automatically selects appropriate version
          command: build
          args: --release --strip
      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: wheels-linux-${{ matrix.target }}
          path: dist

common errors and solutions

glibc version errors

error:

ImportError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.18' not found

cause: wheel built with newer glibc than target system has

solutions:

# option 1: build in docker
docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin:latest build --release

# option 2: skip auditwheel (not for pypi)
maturin build --release --auditwheel skip

# option 3: use zig
maturin build --release --zig --manylinux 2_17

auditwheel compliance failures

error:

Your library is not manylinux_2_17 compliant because of the presence of
too-recent versioned symbols

cause: native build links against system glibc

solution: always build in controlled environment (docker or zig)

platform tag issues

error:

ERROR: package-0.1.0-cp311-cp311-linux_x86_64.whl is not a supported wheel on this platform.

cause: generic linux tag not accepted by pypi

solutions:

  • ensure manylinux compliance via docker/zig
  • use --compatibility pypi flag
  • verify with auditwheel show dist/*.whl

python linking errors

error:

undefined reference to `_Py_Dealloc'
undefined reference to `_Py_NoneStruct'

cause: missing python development headers or incorrect cargo configuration

solution:

# Cargo.toml
[dependencies.pyo3]
version = "0.24"
features = ["extension-module", "abi3-py311"]  # abi3 for forward compatibility

[lib]
name = "your_module"
crate-type = ["cdylib"]

pypi publishing

authentication setup

option 1: environment variable (recommended)

export MATURIN_PYPI_TOKEN="pypi-YOUR_TOKEN_HERE"
maturin publish

option 2: .pypirc configuration

# ~/.pypirc
[pypi]
username = __token__
password = pypi-YOUR_TOKEN_HERE

important: username must be __token__ (lowercase), not __Token__

publishing workflow

#!/bin/bash
# complete publishing workflow

# 1. build wheels in docker
docker run --rm -v "$(pwd)":/io -w /io \
    ghcr.io/pyo3/maturin:latest \
    build --release --strip --manylinux 2_28 -o dist

# 2. test locally
pip install dist/*.whl
python -c "import your_module; print(your_module.__version__)"

# 3. upload to testpypi first
export MATURIN_PYPI_TOKEN="pypi-test-YOUR_TEST_TOKEN"
maturin upload --repository testpypi dist/*.whl

# 4. test from testpypi
pip install -i https://test.pypi.org/simple/ your-package

# 5. upload to production pypi
export MATURIN_PYPI_TOKEN="pypi-YOUR_PRODUCTION_TOKEN"
maturin upload dist/*.whl

common publishing errors

403 forbidden:

  • verify token has correct scope
  • ensure project name matches pypi registration
  • check token hasn’t expired

invalid authentication:

  • use __token__ as username (not __Token__)
  • ensure no control characters in token
  • verify token starts with pypi-

debugging commands

verify glibc requirements

# check system glibc version
ldd --version

# inspect wheel metadata
unzip -l dist/*.whl | grep WHEEL
python -m zipfile -l dist/*.whl

# check wheel compliance
auditwheel show dist/*.whl

# verify glibc symbol versions (critical for debugging)
objdump -T target/release/lib*.so | grep GLIBC | sort -u

# example output showing problematic symbols:
# 0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.29) log
# 0000000000000000 DF *UND* 0000000000000000 (GLIBC_2.34) pthread_key_create
# these symbols would prevent running on systems with glibc < 2.34

test wheel installation

# create clean test environment
python -m venv test_env
source test_env/bin/activate

# install and test
pip install dist/*.whl
python -c "import your_module"

# check linked libraries
ldd test_env/lib/python*/site-packages/your_module*.so

platform-specific notes

macos

no manylinux concerns, but ensure universal2 wheels for intel and apple silicon:

# build universal2 wheel
maturin build --release --universal2

# or separate builds
maturin build --release --target x86_64-apple-darwin
maturin build --release --target aarch64-apple-darwin

windows

use github actions or appropriate visual studio toolchain:

- uses: PyO3/maturin-action@v1
  with:
    target: x86_64-pc-windows-msvc
    command: build
    args: --release

arm architecture

cross-compilation required for aarch64:

# using docker
docker run --rm \
    -v "$(pwd)":/io \
    -w /io \
    ghcr.io/pyo3/maturin:latest \
    build --release \
    --target aarch64-unknown-linux-gnu \
    --manylinux 2_28 \
    -o dist

# using zig
maturin build --release --zig \
    --target aarch64-unknown-linux-gnu \
    --manylinux 2_28

troubleshooting decision tree

build fails with glibc error?
├── yes → build in manylinux docker container
│   ├── still fails? → check rust version >= 1.64
│   └── works → use this approach for ci/cd
└── no → different issue
    ├── linking errors? → check pyo3 features in cargo.toml
    ├── upload fails? → check pypi token configuration
    └── import fails? → verify wheel platform compatibility

best practices checklist

  • always build in manylinux docker container for releases
  • use --strip flag to reduce wheel size
  • test on oldest supported python version (e.g., 3.11)
  • enable abi3 for forward compatibility
  • use --compatibility pypi for compliance check
  • test wheels before publishing
  • document minimum glibc requirements in readme
  • use github actions for automated builds
  • keep build scripts in repository
  • version tag releases appropriately

build script example

create build-wheels.sh in your repository:

#!/bin/bash
set -e

# default to manylinux_2_28 for broad compatibility
MANYLINUX_VERSION=${1:-2_28}

echo "building manylinux${MANYLINUX_VERSION} wheels..."

# clean previous builds
rm -rf dist/

# build wheels in docker
docker run --rm \
    -v "$(pwd)":/io \
    -w /io \
    ghcr.io/pyo3/maturin:latest \
    build --release \
    --strip \
    --manylinux ${MANYLINUX_VERSION} \
    -o dist

# verify wheel compliance
for wheel in dist/*.whl; do
    echo "checking: $wheel"
    docker run --rm \
        -v "$(pwd)":/io \
        -w /io \
        ghcr.io/pyo3/maturin:latest \
        auditwheel show "$wheel"
done

echo "wheels built successfully in dist/"
ls -la dist/

references

official documentation

docker images

github actions

community resources


last updated: august 11, 2025 based on: rust 1.64+, maturin 1.8.3, pyo3 0.24.0 tested on: ubuntu 25.04 (glibc 2.41), debian 13 trixie (glibc 2.41)

on this page