pixi vs conda-lock for reproducible spatial envs

This page answers one question: for a reproducible GDAL/PROJ build environment, should you adopt pixi — an all-in-one project manager with a unified multi-platform lock — or conda-lock, which pins an existing environment.yml into per-platform lock files, and where does each fit an existing conda workflow versus a greenfield project? It sits inside the Dependency Resolution and Lockfiles cluster of the Modern Python Build Tooling & Wheel Configuration reference, and gives you a decision table, the migration cost, and the CI ergonomics of each.

pixi and conda-lock compared across five reproducibility concerns Across lock scope, PyPI integration, task running, existing-workflow fit, and CI setup, pixi provides a unified multi-platform lock, native PyPI plus conda solving, a built-in task runner, and a greenfield fit, while conda-lock produces per-platform locks from an existing environment.yml, integrates PyPI weakly, has no task runner, and fits an established conda workflow with minimal change. pixi conda-lock lock scope PyPI + conda task runner existing env.yml CI setup unified multi-platform native, one solve built in adopt / migrate single action per-platform files weak none drop-in pin conda + step

Context & Root Cause

Both tools produce a committed lock that pins the native spatial stack, but they start from opposite ends. conda-lock is a pinning tool: you keep your existing environment.yml, and conda-lock resolves it into explicit per-platform lock files (conda-linux-64.lock, etc.) that conda/mamba install verbatim. It changes nothing about how you already work — it just freezes it. pixi is a project manager: it introduces its own pixi.toml manifest and pixi.lock, solves conda and PyPI dependencies in one pass, adds a task runner, and manages the environment lifecycle end to end.

For geospatial builds the decision usually comes down to two questions: do you already have a conda workflow you want to keep, and do you need PyPI-only packages (like a niche pyproj build) solved together with conda’s GDAL? An established team with a working environment.yml gets reproducibility fastest with conda-lock; a new project, or one that wants a single tool for environments, tasks, and locking, is better served adopting pixi. This is the tool-choice refinement of the concepts in Dependency Resolution and Lockfiles.

The decision

Concern pixi conda-lock
Manifest New pixi.toml Reuses environment.yml
Lock format One unified pixi.lock (all platforms) One file per platform
conda + PyPI Solved together in one graph conda-first; PyPI weaker
Task runner Built in (pixi run) None (bring your own)
Migration cost Adopt a new manifest Near zero — pin what you have
CI setup-pixi action, one step conda + a conda-lock install step
Best fit Greenfield / all-in-one Existing conda workflow to freeze

Choose conda-lock when you have a working conda environment and only want to make it reproducible with minimal change. Choose pixi when you are starting fresh, want conda and PyPI resolved in one lock, or value the integrated task runner for driving pixi run build-wheel. Neither is more “correct” — they optimize for migration cost versus integration.

Setup side by side

conda-lock — freeze an existing environment

# environment.yml already exists; pin it for three platforms
conda-lock lock -f environment.yml -p linux-64 -p osx-arm64 -p win-64
git add conda-*.lock
# install verbatim in CI — no re-solve
conda-lock install --name build conda-linux-64.lock

pixi — adopt a project manifest

pixi init && pixi add "gdal>=3.8,<3.9" "proj>=9.3,<9.4" python
pixi lock                 # writes the unified pixi.lock
git add pixi.toml pixi.lock
pixi install --locked     # install from the lock in CI

Both then feed the same wheel build, which still needs auditwheel/delocate repair as configuring pixi environments for wheel building shows.

Verification

# 1. conda-lock: the lock installs and pins exact GDAL
conda-lock install --name t conda-linux-64.lock && conda list -n t gdal
# expected: a single resolved gdal build string
# 2. pixi: the locked install is current and pins the same
pixi install --locked && pixi list | grep -E 'gdal|proj'
# expected: single resolved versions, install succeeds
# 3. Either tool: the same version resolves across platforms
grep -oE '3\.8\.[0-9]+' pixi.lock conda-*.lock 2>/dev/null | sort -u
# expected: one GDAL patch version, not several

Pitfalls & Alternatives

Adopting pixi mid-flight for a large conda team. Migrating a working environment.yml to pixi.toml has a real cost in retraining and CI rewiring. If reproducibility is the only goal, conda-lock reaches it without the migration — reserve pixi adoption for when its task runner and unified lock earn their keep.

Expecting conda-lock to solve PyPI-only packages well. conda-lock is conda-first; a package available only on PyPI is a weak spot. If your GDAL build depends on such packages, pixi’s unified solve avoids the pip-on-top-of-conda hazard warned about in configuring pixi environments for wheel building.

Re-solving in CI with either tool. The whole point is to install from the committed lock. Running a fresh solve on the runner reintroduces the drift the lock exists to prevent — use --locked (pixi) or conda-lock install (never a bare conda env create).