Three Ways to Export a Conda Environment, and When Each One Lies to You
Conda has three ways to export an environment, and each answers a different reproducibility question. Here's how to choose between full export, --from-history, and explicit specs — plus the channel discipline that makes any of them work.
16 Mar 2026, 22:26 UTC

The environment that only works on your laptop
You hand a teammate your environment.yml, they run conda env create -f environment.yml, and the solver either grinds forever or fails with a conflict you've never seen. The file worked for you — because it was generated on your machine, for your operating system, from your channel configuration. Reproducibility in conda isn't one problem; it's three, and each export command answers a different one.
The thesis: pick the export format based on where the environment needs to be rebuilt, not on which command you remember first.
Full export: a photograph, not a recipe
conda env export writes every installed package with exact versions and build strings — something like numpy=1.26.4=py312h.... That's a faithful snapshot of your machine, which is exactly the problem. Build strings and platform-specific packages (think vs2015_runtime on Windows or libgfortran variants on Linux) frequently don't exist on other operating systems, so the file breaks the moment it crosses platforms.
Use full export as an audit artifact — "what was actually installed when we got this result" — not as the file you commit for teammates.
From-history: the portable recipe
conda env export --from-history records only the packages you explicitly asked for, producing a small, readable YAML. Because the solver re-resolves dependencies on the target machine, this file travels well between Linux, macOS, and Windows. The trade-off is determinism: re-solving next month may pull newer dependency versions, so "reproducible" here means "same intent," not "same bits."
One gotcha: anything installed with pip inside the environment is invisible to --from-history. Those packages need a pip: section in the YAML or a separate requirements.txt.
Explicit specs: the exact clone
For same-platform reproduction — CI runners, a production clone — skip the solver entirely:
# On the source machine (Linux, in the active env):
conda list --explicit > spec-linux.txt
# On the target machine (same OS), any user with conda:
conda create --name prod-clone --file spec-linux.txtThe explicit file lists exact package URLs, so creation is fast and deterministic — no solving, no surprises. The limitation is hard: it's platform-locked and channel-URL-locked. Don't expect it to work across operating systems.
Channels: the silent variable
Even a perfect YAML fails if the target machine mixes defaults and conda-forge differently than yours. Mixing channels for the same packages is a classic source of ABI conflicts that only surface at install time on a fresh machine. Pick one primary channel (conda-forge is the common community choice), list it in the YAML, and enforce priority:
conda config --set channel_priority strict
conda config --show channels # verify before committing the YAMLA workflow that holds up
- Hand-maintain a minimal
environment.yml: name, one channel, top-level dependencies, pinned Python version. - Generate per-platform explicit specs (
spec-linux.txt,spec-osx.txt) for CI and production. - Commit both. The YAML communicates intent; the specs guarantee bits.
- Verify quarterly: create a fresh env from the YAML in a clean container and diff against the explicit spec.
Note that solver behavior and speed vary across conda versions (the libmamba-based solver made large solves dramatically faster), so check conda --version before blaming your YAML for a slow solve. The actionable step today: run both conda env export and conda env export --from-history in a test environment and diff them. The gap between those two files is exactly your reproducibility risk.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.