Written and maintained by CASRAI Editorial Board
Last updated
Almost every non-trivial scientific Python or bioinformatics environment ends up needing both a conda-style package (from conda-forge or Bioconda) and a package that only exists on PyPI. Mixing conda or mamba installs with pip installs in the same environment is normal and often unavoidable — but it is also one of the most common ways a working environment quietly turns into a broken one. The failure mode is not random: it follows directly from how conda’s solver works, and it is entirely avoidable once you understand the order of operations and how to declare both package sources correctly in a single environment.yml.
Why pip-installed packages are invisible to conda’s solver
Conda (and mamba, which uses the same environment model and reads the same environment.yml files) manages an environment by solving a dependency graph: given the packages you’ve asked for, it works out a mutually compatible set of versions across every package already in the environment, then records that resolution in its own package metadata. That metadata is conda’s, not the operating system’s or Python’s — it lives in conda-meta/ inside the environment and is what every subsequent conda install or conda update reads before deciding what to change.
Pip does not write to that metadata. When you run pip install inside an active conda environment, pip installs into that environment’s site-packages the same way it would into any Python environment, using its own dependency resolver and its own record of what’s installed (Python’s standard packaging metadata). Conda’s solver has no visibility into that. As conda’s own documentation puts it, conda “has limited abilities to control packages that it did not install” — from conda’s point of view, anything pip put there is opaque. conda list will show pip-installed packages (labelled with a pypi channel), but conda cannot verify their dependencies were satisfied the way it would for its own packages, and it will happily overwrite them without knowing what it’s breaking.
What actually breaks when you conda-install after pip
This asymmetry is why order matters. If you pip install a package and then later run conda install for something else, conda’s solver may decide to upgrade or downgrade a shared dependency to satisfy its own graph — a dependency that your pip-installed package needs at a specific version, but that conda doesn’t know is load-bearing for it. The official conda documentation is explicit about the risk: “running conda after pip has the potential to overwrite and potentially break packages installed via pip.” The reverse can also happen: pip, run after conda, can upgrade or remove a package that conda-installed software depends on, since pip’s resolver doesn’t consult conda’s metadata either.
In practice this shows up as import errors after an apparently unrelated install, a compiled extension suddenly pointing at the wrong shared library version, or a workflow manager failing mid-pipeline on a rule that worked yesterday. None of this is a bug in conda or pip individually — it’s the predictable result of two independent solvers managing the same site-packages directory with no shared source of truth, and it’s exactly the kind of hidden environment drift that undermines computational reproducibility when a captured environment turns out not to reinstall the way it did originally.
The recommended order of operations
The fix conda’s own documentation recommends is procedural, not a flag or a config setting: install as many requirements as possible with conda (or mamba) first, and use pip only for what conda-forge/Bioconda genuinely don’t provide, as the last step. Concretely:
- Create the environment and install every package that’s available via conda-forge or Bioconda first — this includes anything with compiled/binary dependencies (numerical libraries, aligners, anything linking against system BLAS/LAPACK, CUDA-dependent packages), where conda’s binary builds avoid the compiler toolchain and ABI issues pip/PyPI wheels can run into.
- Only then install the remaining PyPI-only packages with pip, inside that same activated environment.
- Leave pip’s default
--upgrade-strategy only-if-neededalone (don’t pass--upgradebroadly), and avoidpip install --userinside a conda environment — it installs outside the environment’s ownsite-packagesand defeats the isolation conda is providing. - If you later need to add more conda packages to an environment that already has pip installs in it, treat that as a signal to recreate the environment from a spec file rather than layering another
conda installon top. Once pip has modified the environment, conda’s solver is working from stale assumptions about what’s actually there, and the safest way to get a consistent environment back is to rebuild it in the correct order, not to keep patching it.
This same ordering logic holds whether you use conda or mamba as the solver — mamba is a faster, C++ reimplementation of conda’s core CLI (and since conda 23.10, its libmamba solving engine is conda’s own default too), but it manages the identical conda-meta/ metadata and has the identical blind spot toward anything pip installed. See Mamba vs Conda for how the two tools compare on solving speed specifically; the pip-interaction behavior described here is the same for both.
Declaring both conda and pip packages in one environment.yml
For a reproducible, shareable environment, don’t rely on remembering the right install order by hand — declare it once in environment.yml, which both conda and mamba read identically. Conda’s package-spec format supports a nested pip: list inside the top-level dependencies: list, which is installed via pip after every other listed conda dependency has been resolved and installed — the file itself encodes the correct order:
name: my-project
channels:
- conda-forge
- bioconda
dependencies:
- python=3.11
- numpy
- pandas
- samtools
- pip
- pip:
- some-pypi-only-package==2.3.1
- another-pip-package
Two details matter here. First, pip itself must be listed as a regular conda dependency (as shown above) so that a pip binary actually exists inside the environment for the nested list to use — without it, environment creation will fail trying to run the pip: section. Second, keep the split intentional: everything that has a conda-forge or Bioconda build belongs in the top-level dependencies: list, and the pip: block is reserved for packages that genuinely don’t have one. Treating pip: as a catch-all for anything you didn’t feel like looking up defeats the point — you lose conda’s binary compatibility guarantees for packages that didn’t need to be pip-only in the first place.
Build the environment from the file with either tool — conda env create -f environment.yml or the equivalent mamba env create -f environment.yml — and both will install the top-level conda dependencies first, then run pip against the nested list, in the order the file already encodes.
Checking and exporting a mixed environment
A few commands are worth knowing once an environment has both kinds of packages in it:
conda listshows every package in the environment, including pip-installed ones (marked with thepypichannel rather thanconda-forge/bioconda), so you can see at a glance what came from where.conda env exportinspects the environment’s installed packaging metadata and will include apip:section listing the pip-installed packages alongside the conda ones, so a single exported file captures both — useful for committing a working environment’s exact state to version control as part of a broader software management plan. Add--no-buildsif you want the export to be more portable across platforms (dropping conda’s exact build-string pins while keeping version numbers).pip checkverifies that pip’s own view of installed-package dependencies is internally consistent — it won’t catch a conda-side conflict, but it’s a quick way to confirm pip’s installs at least satisfy each other’s stated requirements.
None of these substitute for getting the install order right in the first place; they’re diagnostic tools for confirming (or debugging) an environment after the fact, not a way to safely mix installs in arbitrary order. A pinned, version-controlled environment.yml alongside a tool like DVC for the data and outputs it produces is the more durable pattern — and it’s the kind of environment provenance a research software engineer or informatics-support team will expect to see before they can debug or rebuild a pipeline that isn’t theirs.
Frequently asked questions
Can I install pip packages before conda packages, if I’m careful?
Technically the environment won’t refuse it, but it inverts the safety the ordering is designed to provide: any conda package you install afterward can silently override something pip already put in place, since conda’s solver still won’t check pip’s installs before deciding what to change. There’s no equivalent safe way to do it in reverse — conda-first, pip-last is the only ordering where each tool’s install pass is aware of what came before it.
Does mamba handle the pip interaction any differently than conda?
No. Mamba is a reimplementation of conda’s CLI and solver engine, but it operates on the same environment and the same conda-meta/ metadata conda does, so it has the identical blind spot toward pip-installed packages. Everything above applies equally whether you run conda install/conda env create or the mamba equivalents.
My environment is already broken from conda and pip stepping on each other — how do I fix it?
Rebuild it rather than trying to reconcile it in place. Write (or fix) an environment.yml with the conda-forge/Bioconda-available packages in the top-level dependencies: list and the PyPI-only ones in the nested pip: list, then create a fresh environment from that file. Patching a mixed environment that’s already inconsistent tends to compound the problem rather than resolve it, since each subsequent install is still working from an incomplete picture.
If a package is available on both conda-forge and PyPI, which should I use?
Prefer the conda-forge/Bioconda build when one exists, particularly for anything with compiled or binary dependencies (numerical/scientific libraries, aligners and other bioinformatics binaries). Conda’s binary packaging manages the underlying compiled libraries and their compatibility as part of the same dependency graph; installing the equivalent PyPI wheel bypasses that and is exactly the kind of package that later triggers a conda/pip conflict. Reserve pip for packages that are genuinely PyPI-only — it keeps the environment closer to the kind of reusable, well-specified software a FAIR software checklist expects, rather than a pile of ad hoc pip installs nobody can reconstruct.








