Smoke-testing GDAL wheels in a clean container
This page answers one question: how do you prove a GDAL/PROJ wheel actually works before publishing it — by installing it in a container that has none of your build dependencies and running a functional transform, so you catch missing bundled libraries and missing datum data that the build box always hides? It sits inside the Testing and Validating Spatial Wheels cluster of the Modern Python Build Tooling & Wheel Configuration reference, and gives you the container commands, a functional test that exercises GDAL and PROJ, and the musllinux variant.
Context & Root Cause
The build machine is the worst place to test a wheel, because it is contaminated: it has GDAL, PROJ, GEOS, and their data directories installed to make the build, so a wheel that forgot to bundle a library or a data file imports anyway by silently borrowing the host’s copy. The user’s machine has none of that, so the defect surfaces there first — the exact outcome testing is meant to prevent. A clean container reproduces the user’s environment: a minimal python:3.12-slim image with no geospatial anything, into which the wheel is installed and then made to do real work.
“Real work” matters as much as the clean environment. A bare import proves the libraries load, but not that PROJ can find its datum database or that GDAL can open a dataset — both need the data files that repair tools never bundle. A functional smoke test runs an actual coordinate transform and opens a raster, exercising the code paths that DataDirError and driver-registration bugs hide behind. This is the load-and-function half of the validation ladder in Testing and Validating Spatial Wheels.
Solution / Fix
This targets Docker, python:3.12-slim (glibc) or python:3.12-alpine (musl), and a repaired wheel in dist/.
1. Install and import in a pristine glibc container
docker run --rm -v "$PWD/dist:/d" python:3.12-slim bash -c '
pip install --no-cache-dir /d/*manylinux*.whl &&
python -c "from osgeo import gdal, ogr, osr; print(\"import ok\", gdal.__version__)"'
# expected: import ok 3.8.x
--no-cache-dir and the bare image guarantee nothing but the wheel provides GDAL.
2. Run a functional transform and raster open
docker run --rm -v "$PWD/dist:/d" python:3.12-slim bash -c '
pip install --no-cache-dir /d/*manylinux*.whl &&
python - <<PY
import pyproj
from osgeo import gdal
# PROJ: needs proj.db — fails with DataDirError if data unbundled
t = pyproj.Transformer.from_crs(4326, 3857, always_xy=True)
print("transform:", t.transform(5.0, 52.0))
# GDAL: driver registration + in-memory dataset
gdal.UseExceptions()
ds = gdal.GetDriverByName("MEM").Create("", 4, 4, 1)
print("gdal mem dataset:", ds.RasterXSize, ds.RasterYSize)
PY'
# expected: a coordinate pair and "gdal mem dataset: 4 4"
3. Make it a reusable script
# smoke.sh — parametrized on image and wheel glob
IMG="${1:-python:3.12-slim}"; GLOB="${2:-*manylinux*}"
docker run --rm -v "$PWD/dist:/d" "$IMG" bash -c \
"pip install -q /d/$GLOB.whl && python /d/../ci/smoke_test.py"
Verification
# 1. glibc smoke test passes
bash ci/smoke.sh python:3.12-slim '*manylinux*' # expected: exit 0, prints transform + dataset
# 2. musllinux wheel must be tested on Alpine, not slim
bash ci/smoke.sh python:3.12-alpine '*musllinux*' # expected: exit 0
# 3. Oldest supported interpreter (abi3 floor) also imports
docker run --rm -v "$PWD/dist:/d" python:3.9-slim \
bash -c "pip install -q /d/*manylinux*.whl && python -c 'from osgeo import gdal'"
# expected: no error — proves the Stable ABI floor holds
A clean exit on glibc, musl, and the 3.9 floor confirms the wheel is genuinely self-contained across the platforms it claims.
Pitfalls & Alternatives
Testing in the build container. Reusing the build image (which has GDAL) defeats the entire purpose — the wheel passes by borrowing host libraries. Always use a minimal image with no geospatial packages.
Import-only smoke tests. A bare import misses DataDirError and driver-registration failures because those trigger only on use. Run a transform and a dataset operation, not just an import.
Testing musllinux on glibc. A musllinux wheel installed in python:3.12-slim may pip-resolve to a different manylinux wheel or fail to load. Match the image libc to the wheel tag — the distinction is the subject of manylinux2014 vs musllinux for spatial libs.
Related
- Testing and Validating Spatial Wheels — the parent guide placing this in the four-gate validation ladder.
- Verifying wheel tags with auditwheel show — the static gate that runs before this runtime one.
- Bundling proj.db and datum grids in a wheel — the fix when the functional transform raises
DataDirError.