Skip to content

enhancement(packaging): add musllinux wheels for Alpine / musl Linux users #333

Description

@MAfarrag

Context

feat/cog (PR #243) ships Linux wheels for glibc Linux only. Alpine Linux and other musl-libc distros
(linuxserver/* Docker images, distroless musl, postmarketOS, Void-musl, Adelie, etc.) fall through to the sdist,
which needs system GDAL >= 3.10 + a C/C++ compiler at install time — and Alpine's apk gdal-dev package is often
several versions behind upstream, so the sdist install path is fragile there.

The recommended workaround today is conda-forge (which is glibc-agnostic and works on Alpine via the
conda-forge/linux-64 channel). But that requires bootstrapping a conda env on what's typically a deliberately-minimal
Alpine container — defeats the purpose of choosing Alpine in the first place.

Status: reactive — revisit when an Alpine user files an issue. As of 2026 there's no recorded pyramids-gis
Alpine user.

Related: #331 (master bundling doc), PR #243.

Problem / Current Behaviour

[tool.cibuildwheel].skip in pyproject.toml includes "*musllinux*", so cibuildwheel never attempts a musllinux
build. Even if we removed that exclusion, the pipeline would fail before producing a wheel — see "Why we don't build
this today" below.

Affected locations

File Symbol Notes
pyproject.toml [tool.cibuildwheel].skip currently excludes *musllinux*
pyproject.toml [tool.pixi.feature.wheel-build.target] no musllinux entry — conda-forge has no musllinux artifacts
.github/workflows/build-wheels.yml build-linux-wheels matrix no musllinux row
ci/setup-gdal-from-pixi.sh pixi-based extraction won't run inside a musllinux_1_2 container (musl libc, no glibc)
ci/install-and-vendor-osgeo.py vendor step depends on the pixi extraction above

Why we don't build this today

conda-forge does not ship gdal (or any of its transitive native deps) for musl-libc. The conda-forge feedstock
targets linux-64 / linux-aarch64 (glibc), osx-{64,arm64}, win-64. There's no linux-musl-64 channel. So the
entire pixi-based extraction + auditwheel-repair flow this repo uses for manylinux_2_39 doesn't have an upstream
counterpart for musllinux.

Two options to unblock:

  1. Wait for conda-forge to ship musllinux artifacts — proposed periodically on conda-forge/conda-forge.github.io
    but no concrete ETA. Tracking on the conda-forge side would be the cleanest path; if that lands, this becomes a
    mechanical config change (~half day).
  2. Build GDAL from source in the musllinux_1_2 container — feasible but expensive: the GDAL build chain pulls
    PROJ + GEOS + libtiff + libgeotiff + NetCDF-C + HDF5 + libxml2 + libcurl + libsqlite3 + libexpat + libpng + libjpeg
    • libzstd + libwebp, every one of which has its own configure/build dance. Realistic effort: ~1 week to get a
      working build, plus ongoing maintenance cost any time the GDAL version we ship moves and one of those deps changes
      its build requirements.

Steps to Reproduce

# Spin up an Alpine container, try to install pyramids-gis from PyPI:
docker run --rm -it alpine:3.20 sh -c '
    apk add --no-cache python3 py3-pip
    pip install pyramids-gis --break-system-packages
'
# pip selects the sdist (no wheel matches). Sdist install then fails on
# missing system GDAL headers because Alpine's gdal-dev (when present)
# is typically older than the >=3.10 floor.

Proposed Solution

Path A — Wait for conda-forge musllinux (recommended)

When conda-forge ships gdal for linux-musl-64 / linux-musl-aarch64:

  1. Drop "*musllinux*" from [tool.cibuildwheel].skip.
  2. Add [tool.pixi.feature.wheel-build.target.linux-musl-{64,aarch64}.dependencies] with the same GDAL pin as
    linux-64.
  3. Add a build-linux-musl-wheels job to .github/workflows/build-wheels.yml (matrix: x86_64, aarch64).
  4. Add musllinux rows to the test-wheels matrix (use alpine:3.20 as the base image).

This is the same shape as the existing manylinux job, just with musllinux_1_2 instead of manylinux_2_39.
Effort: ~half day mechanical work once upstream lands.

Path B — From-source GDAL inside musllinux_1_2

If Alpine demand spikes before conda-forge ships, alternative is a from-source GDAL build script under
ci/build-gdal-musllinux.sh. Build the chain into /usr/local, point cibuildwheel's before-all at it, then the
before-build + repair steps work unchanged. Effort: ~1 week for the initial build script + validation.

Both paths converge on the same downstream artifact: pyramids_gis-X.Y.Z-cp3NN-cp3NN-musllinux_1_2_{x86_64,aarch64}.whl.

Out of Scope

  • musllinux on macOS / Windows (not a real platform).
  • Lowering the musllinux baseline below musllinux_1_2 (= musl 1.2, Alpine 3.13+). Earlier musl versions are EOL.
  • Building from source on manylinux too — separate concern; manylinux already has conda-forge.

Effort Estimate

Size: L if Path B (from-source GDAL build); S if Path A (conda-forge musllinux lands).
Rationale: Path A is mechanical config replication. Path B requires building 12+ native libs in a musl
container, validating each one against GDAL's version pins, and absorbing the ongoing maintenance cost of those build
recipes when upstream versions shift.

Definition of Done

  • musllinux_1_2_x86_64 + musllinux_1_2_aarch64 wheels produced for every supported Python (cp311-cp314 today).
  • pip install pyramids-gis on alpine:3.20 resolves the musllinux wheel from PyPI (no sdist fallback).
  • python -c 'import pyramids; from osgeo import gdal; print(gdal.__version__)' inside Alpine works.
  • test-wheels matrix in build-wheels.yml includes Alpine rows that pass.
  • docs/how-to/wheel-build-flow.md "Platform coverage" matrix updated: Alpine row flips from "no" to "yes".
  • docs/installation.md updated.
  • If Path B: ci/build-gdal-musllinux.sh documented (script header explaining each build step + the version pins
    it produces).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    devinstallation, cienhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions