The practical reference for compiling, testing, and shipping Python geospatial C-extensions — the native code behind GDAL, PROJ, and PyProj — with modern CI/CD and wheel workflows.
🌍 Read it at geospatial-ci-cd.org →
Shipping a Python geospatial package means shipping compiled C and C++ to machines you will never see. Bindings for GDAL, PROJ, GEOS, and the raster and vector engines beneath them are not pure Python — they are platform-specific shared objects that must agree, byte for byte, with both the CPython interpreter that imports them and the native libraries they link against. When that agreement breaks, it breaks on a user's laptop or a production container as an ImportError or a segfault, not in your CI logs.
geospatial-ci-cd.org exists to make that native packaging reproducible instead of fragile. Every guide ships exact, copy-pasteable configuration and validation commands so you can move from a broken ImportError to a portable, audited wheel that imports identically on a laptop, a manylinux runner, and a serverless container.
Python GIS developers, package maintainers, DevOps and platform engineers, and data-platform teams who build or distribute anything that links GDAL, PROJ, GEOS, or their kin — pyproj, rasterio, shapely, fiona, or hand-rolled extensions.
The material is organised around two areas, each broken into focused topics and step-by-step deep dives:
The binary contract itself — the rules that decide whether a compiled wheel imports at all:
- The CPython Stable ABI (
abi3) and version pinning that collapses your interpreter matrix - Vendoring PROJ and GDAL vs. relying on system libraries, and the wheel-size trade-offs
- Shared-library path resolution —
RPATH,RUNPATH,$ORIGIN, and the loader-precedence rules - Memory management — reference counting, GIL release around long transforms, and native-heap leaks
- Cross-compilation — sysroots, toolchain files, and building
aarch64wheels without QEMU - Debugging import & linker failures, symbol visibility & namespace isolation, and platform-specific ABI quirks (macOS
universal2, Windows DLL loading) - Security boundaries for compiling and running untrusted spatial input
The pipeline that turns a manifest into a tagged, repaired, published artifact:
pyproject.toml, scikit-build-core, and CMake for spatial extensions- manylinux / musllinux base images and the glibc floor every Linux wheel inherits
- Pixi / conda environment isolation and reproducible lockfiles
- CI matrix recipes — GitHub Actions and GitLab CI, side by side
- Testing & validation —
auditwheel show, clean-container smoke tests, functional checks - Publishing & distribution — trusted publishing to PyPI and wheel-size limits
- Caching strategies that keep the build matrix affordable
- Copy-pasteable, not hand-wavy. Real
pyproject.tomltables, CMake toolchain files, GitHub Actions and GitLab CI YAML, and the exactauditwheel/delocate/delvewheelcommands. - Error-message driven. Failure modes quote the verbatim
ImportError/OSErrortext so the fix matches what you actually paste into a search bar. - Hand-authored diagrams. Every guide carries an original, theme-aware SVG that explains the hardest idea on the page.
- Fast, accessible, offline-friendly. A static site with a tight internal link graph, structured data, and a service worker.
| Start here | |
|---|---|
| 🌍 Live site | www.geospatial-ci-cd.org |
| 🧩 C-Extension & ABI Fundamentals | /geospatial-c-extension-fundamentals-abi-architecture/ |
| 🛠️ Build Tooling & Wheels | /modern-python-build-tooling-wheel-configuration/ |
This repository contains the source for the site — content in Markdown, rendered to a static site with Eleventy and deployed to Cloudflare.
npm install # install dependencies
npm run serve # local dev server with live reload
npm run build # build the static site into _site/Released under the MIT License.
Built for Python GIS developers, package maintainers, DevOps, and data-platform teams.