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:
- 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).
- 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:
- Drop
"*musllinux*" from [tool.cibuildwheel].skip.
- Add
[tool.pixi.feature.wheel-build.target.linux-musl-{64,aarch64}.dependencies] with the same GDAL pin as
linux-64.
- Add a
build-linux-musl-wheels job to .github/workflows/build-wheels.yml (matrix: x86_64, aarch64).
- 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
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
apkgdal-devpackage is oftenseveral 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 theconda-forge/linux-64channel). But that requires bootstrapping a conda env on what's typically a deliberately-minimalAlpine 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-gisAlpine user.
Related: #331 (master bundling doc), PR #243.
Problem / Current Behaviour
[tool.cibuildwheel].skipinpyproject.tomlincludes"*musllinux*", so cibuildwheel never attempts a musllinuxbuild. 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
pyproject.toml[tool.cibuildwheel].skip*musllinux*pyproject.toml[tool.pixi.feature.wheel-build.target].github/workflows/build-wheels.ymlbuild-linux-wheelsmatrixci/setup-gdal-from-pixi.shmusllinux_1_2container (musl libc, no glibc)ci/install-and-vendor-osgeo.pyWhy 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 feedstocktargets
linux-64/linux-aarch64(glibc),osx-{64,arm64},win-64. There's nolinux-musl-64channel. So theentire pixi-based extraction + auditwheel-repair flow this repo uses for
manylinux_2_39doesn't have an upstreamcounterpart for musllinux.
Two options to unblock:
conda-forge/conda-forge.github.iobut 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).
musllinux_1_2container — feasible but expensive: the GDAL build chain pullsPROJ + GEOS + libtiff + libgeotiff + NetCDF-C + HDF5 + libxml2 + libcurl + libsqlite3 + libexpat + libpng + libjpeg
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
Proposed Solution
Path A — Wait for conda-forge musllinux (recommended)
When conda-forge ships
gdalforlinux-musl-64/linux-musl-aarch64:"*musllinux*"from[tool.cibuildwheel].skip.[tool.pixi.feature.wheel-build.target.linux-musl-{64,aarch64}.dependencies]with the same GDAL pin aslinux-64.build-linux-musl-wheelsjob to.github/workflows/build-wheels.yml(matrix:x86_64,aarch64).test-wheelsmatrix (usealpine:3.20as the base image).This is the same shape as the existing manylinux job, just with
musllinux_1_2instead ofmanylinux_2_39.Effort: ~half day mechanical work once upstream lands.
Path B — From-source GDAL inside
musllinux_1_2If 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'sbefore-allat it, then thebefore-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_1_2(= musl 1.2, Alpine 3.13+). Earlier musl versions are EOL.Effort Estimate
Size:
Lif Path B (from-source GDAL build);Sif 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_aarch64wheels produced for every supported Python (cp311-cp314 today).pip install pyramids-gisonalpine:3.20resolves the musllinux wheel from PyPI (no sdist fallback).python -c 'import pyramids; from osgeo import gdal; print(gdal.__version__)'inside Alpine works.test-wheelsmatrix inbuild-wheels.ymlincludes Alpine rows that pass.docs/how-to/wheel-build-flow.md"Platform coverage" matrix updated: Alpine row flips from "no" to "yes".docs/installation.mdupdated.ci/build-gdal-musllinux.shdocumented (script header explaining each build step + the version pinsit produces).