Conda Environments, Channels & Isolation: The Bioinformatics Setup That Prevents Conflicts
A beginner guide to conda environments, bioconda channels, strict priority, and the libmamba solver for conflict-free bioinformatics on Apple Silicon Macs.
If you have ever installed one bioinformatics tool, watched it upgrade a shared library, and then discovered a different tool suddenly stopped working, you already understand the problem this guide solves. Genomics software is a tangle of shared C libraries, exact Perl and Java versions, and pinned dependencies. Install everything into one place and those pins fight each other.
The fix is isolation. You create one conda environment per tool, and inside that environment lives the exact version and build the tool needs, completely independent of the rest of your system. This is the single most important habit in a bioinformatics setup, and it is the backbone of every other install guide in this series.
This post is the conceptual heart: what environments are, what channels are, why we route around Anaconda's defaults channel, how strict priority and the libmamba solver keep solves fast and correct, and how Apple Silicon fits in. Every version number and every block of terminal output below was captured on a real Apple Silicon Mac (macOS 26.5.2, conda 25.5.1).
Why isolate? One environment per tool
Think of an environment as a private, self-contained folder that holds a tool and all of its dependencies. When you activate it, your shell points at that folder's binaries and libraries. When you deactivate, they vanish from your path. Nothing leaks into the system, and nothing the system does can break the tool.
The payoff is that the exact required build lives in the environment, not on the machine. Tool A can depend on htslib 1.24 while Tool B insists on an older one, and both are happy because they never see each other. This is the same principle that, on Linux, lets a GPU tool ship its own CUDA toolkit inside a conda env and run regardless of the CUDA version installed system-wide.
base environment. Keep base minimal (just conda and its solver) and give every tool its own named env. Polluting base is the fastest way to recreate the exact dependency hell isolation is meant to prevent.Prerequisites
You need a working conda. If you have not installed it yet, follow the companion guide first: Install Miniconda on Apple Silicon. Everything here assumes conda is on your PATH and your prompt shows (base).
Here is the real state of the test machine, so you can compare against yours:
$ conda --version
conda 25.5.1
$ conda info
conda version : 25.5.1
python version : 3.13.5.final.0
solver : libmamba (default)
virtual packages : __archspec=1=x86_64
base environment : /opt/homebrew/Caskroom/miniconda/base (writable)
channel URLs : https://repo.anaconda.com/pkgs/main/osx-64
platform : osx-64
Two things worth noticing. First, the solver is libmamba by default in modern conda, which is the fast dependency solver that used to require installing mamba separately. Second, this base is an x86_64 Miniconda (platform : osx-64) whose default channel is Anaconda's defaults. That second fact is exactly why the next section matters.
The channel problem, and how we route around it
A channel is a repository conda downloads packages from. For bioinformatics you want two, in this order of priority:
- conda-forge — the huge community channel for general-purpose libraries (compilers,
libzlib,openssl, Python itself). - bioconda — the bioinformatics channel (samtools, bwa, GATK, salmon, and thousands more), which depends on conda-forge for its base libraries.
The channel you do not want is Anaconda's defaults. As of recent conda releases, installing from defaults in an organizational or automated context can trip a commercial Terms-of-Service gate that stops the solve dead. On a fresh Miniconda base, defaults is configured out of the box:
$ conda config --show channels channel_priority solver
channel_priority: flexible
channels:
- defaults
solver: libmamba
The clean, permanent fix is to stop using defaults for your tool environments. We do that by passing --override-channels -c conda-forge -c bioconda on every conda create. The --override-channels flag tells conda to ignore whatever channels are configured (including defaults) and use only the ones you list. No defaults, no ToS gate, ever.
conda config --remove channels defaults, or install Miniforge (a conda-forge-first distribution) instead of Miniconda. But --override-channels -c conda-forge -c bioconda is explicit and self-documenting, so we use it in every command in this series.Strict channel priority
You also want strict channel priority so conda never mixes the same package from two channels. With strict priority and the channels ordered conda-forge first, conda-forge always wins ties over bioconda, which is exactly what bioconda's own docs recommend. On the test machine priority was still the default flexible (shown above); switch it once:
conda config --set channel_priority strictStrict priority plus the libmamba solver is the combination that makes solves both fast and conflict-free.
TL;DR — copy-paste install
Here is the whole pattern. Swap mytool and the package spec for whatever you are installing.
# One-time: strict channel priority (conda-forge wins over bioconda)
conda config --set channel_priority strict
# Create an ISOLATED env for ONE tool, pinned, using only conda-forge + bioconda.
# --override-channels ignores 'defaults' so you never hit the Anaconda ToS gate.
conda create -n mytool \
--override-channels -c conda-forge -c bioconda \
"samtools=1.21" -y
# Use it
conda activate mytool
samtools --version
conda deactivate
# Reproduce or remove
conda env export --from-history > mytool.yml # portable, only your explicit specs
conda env remove -n mytool # delete the whole env cleanlyStep by step
1. Create an isolated environment
Every tool gets its own named env. Here is a real creation of a samtools/bcftools environment, using only conda-forge and bioconda. Watch the header: conda resolves against exactly the two channels we asked for, and the platform is osx-arm64 — native Apple Silicon builds, no emulation.
$ conda create -n bu-samtools --override-channels -c conda-forge -c bioconda samtools bcftools htslib -y
Channels:
- conda-forge
- bioconda
Platform: osx-arm64
Collecting package metadata (repodata.json): done
Solving environment: done
## Package Plan ##
environment location: /opt/homebrew/Caskroom/miniconda/base/envs/bu-samtools
added / updated specs:
- bcftools
- htslib
- samtools
The following packages will be downloaded:
package | build
---------------------------|-----------------
bcftools-1.24 | hf623529_1 808 KB bioconda
c-ares-1.34.8 | h84a0fba_0 179 KB conda-forge
htslib-1.24 | hd3c6ec9_0 959 KB bioconda
libpsl-0.22.0 | h92480b3_1 67 KB conda-forge
perl-5.32.1 | 7_h4614cfb_perl5 13.8 MB conda-forge
samtools-1.24 | h36b3a25_1 445 KB bioconda
------------------------------------------------------------
Total: 16.2 MB
The following NEW packages will be INSTALLED:
_openmp_mutex conda-forge/osx-arm64::_openmp_mutex-4.5-7_kmp_llvm
bcftools bioconda/osx-arm64::bcftools-1.24-hf623529_1
bzip2 conda-forge/osx-arm64::bzip2-1.0.8-hd037594_9
c-ares conda-forge/osx-arm64::c-ares-1.34.8-h84a0fba_0
ca-certificates conda-forge/noarch::ca-certificates-2026.6.17-hbd8a1cb_0
gsl conda-forge/osx-arm64::gsl-2.7-h6e638da_0
htslib bioconda/osx-arm64::htslib-1.24-hd3c6ec9_0
... (21 more packages) ...
perl conda-forge/osx-arm64::perl-5.32.1-7_h4614cfb_perl5
samtools bioconda/osx-arm64::samtools-1.24-h36b3a25_1
zstd conda-forge/osx-arm64::zstd-1.5.7-hbf9d68e_6
Notice how each package line names its channel and architecture: bioconda/osx-arm64::samtools-1.24, conda-forge/osx-arm64::perl-5.32.1. bioconda supplies the tools; conda-forge supplies the base libraries and Perl. That layering is the whole reason both channels are listed, with conda-forge first.
On this machine the solve, download, and install of all 31 packages finished in about 27 seconds:
Preparing transaction: done
Verifying transaction: done
Executing transaction: done
#
# To activate this environment, use
#
# $ conda activate bu-samtools
real 26.81
platform : osx-64 in conda info), yet every conda-forge/bioconda env we create resolves native osx-arm64 builds. That is isolation in action — the environment, not the base, decides the architecture and the versions.2. Pin versions for reproducibility
Leaving a spec unpinned means "give me whatever is newest today," which is fine until an update silently changes behavior. Pin the version in the spec so the env is reproducible:
conda create -n bu-samtools \
--override-channels -c conda-forge -c bioconda \
"samtools=1.21" "bcftools=1.21" -y3. Understand what a minimal env looks like
Here is a second, tiny env created the same way — just Python 3.12 from conda-forge — to show the pattern on something simpler. Every package resolves from conda-forge:
$ conda create -y -n demoenv --override-channels -c conda-forge python=3.12
# $ conda activate demoenv
$ conda run -n demoenv python -V
Python 3.12.13
$ conda list -n demoenv | head
bzip2 1.0.8 hd037594_9 conda-forge
ca-certificates 2026.6.17 hbd8a1cb_0 conda-forge
libexpat 2.8.1 hf6b4638_1 conda-forge
libffi 3.5.2 hcf2aa1b_0 conda-forge
liblzma 5.8.3 h8088a28_0 conda-forge
libsqlite 3.53.3 h1b79a29_0 conda-forge
Even this bare Python env pulls native builds (bzip2-1.0.8-hd037594_9 is the same osx-arm64 build the samtools env used) — proof the two envs share nothing but the underlying conda-forge packages they each requested.
Apple Silicon: native arm64, and the Rosetta fallback
Most modern bioinformatics tools now have native osx-arm64 builds, and conda picks them automatically (you saw Platform: osx-arm64 above). Native is faster and needs no emulation, so prefer it.
But channel coverage is per-package. Some recipes still ship only osx-64 (Intel) builds. When that happens you will see a PackagesNotFoundError on Apple Silicon. The fix is to recreate that one env under x86_64 emulation via Rosetta 2, by setting CONDA_SUBDIR=osx-64 for the create, then locking the env to that architecture so later installs stay consistent:
# Only if a tool has no osx-arm64 build. Requires Rosetta 2:
# softwareupdate --install-rosetta
CONDA_SUBDIR=osx-64 conda create -n mytool_x86 \
--override-channels -c conda-forge -c bioconda "sometool" -y
conda activate mytool_x86
conda config --env --set subdir osx-64 # pin this env to x86_64Because envs are isolated, you can freely mix a few osx-64 envs alongside your native osx-arm64 ones on the same machine. To check what any env actually resolved, run conda list --show-channel-urls and read the osx-arm64 versus osx-64 build tags.
osx-arm64 (see the real install output). Use it only when you actually hit a PackagesNotFoundError for a missing arm64 build.Verify the install
After creating the samtools env, activate it and confirm the tools are present and the versions are what you expect:
conda activate bu-samtools
samtools --version | head -3
bcftools --version | head -2samtools 1.24
Using htslib 1.24
Copyright (C) 2026 Genome Research Ltd.
bcftools 1.24
Using htslib 1.24
A version string proves the binary runs, but not that it works. Run a quick smoke test — a tiny synthetic SAM turned into a sorted, indexed BAM and summarized with flagstat:
printf '@HD\tVN:1.6\tSO:coordinate\n@SQ\tSN:chr1\tLN:100\nr1\t0\tchr1\t1\t60\t4M\t*\t0\t0\tACGT\tIIII\n' > tiny.sam
samtools view -b tiny.sam | samtools sort -o tiny.sorted.bam -
samtools index tiny.sorted.bam
samtools flagstat tiny.sorted.bam2 + 0 in total (QC-passed reads + QC-failed reads)
2 + 0 primary
0 + 0 secondary
0 + 0 supplementary
0 + 0 duplicates
0 + 0 primary duplicates
2 + 0 mapped (100.00% : N/A)
2 + 0 primary mapped (100.00% : N/A)
0 + 0 paired in sequencing
0 + 0 read1
0 + 0 read2
0 + 0 properly paired (N/A : N/A)
0 + 0 with itself and mate mapped
0 + 0 singletons (N/A : N/A)
0 + 0 with mate mapped to a different chr
0 + 0 with mate mapped to a different chr (mapQ>=5)
That is a full read/sort/index/count round trip working inside the isolated env. The whole environment weighs in at a modest size on disk:
$ du -sh /opt/homebrew/Caskroom/miniconda/base/envs/bu-samtools
207M /opt/homebrew/Caskroom/miniconda/base/envs/bu-samtools
Conda environments vs. Python's venv
You may have seen Python's built-in venv and wondered how it compares. venv is lighter, but it only isolates Python packages. It cannot install compiled non-Python binaries like samtools, bwa, or GATK — those are not pip-installable in any meaningful way. Here is a real venv for contrast, isolating a Python library only:
python3 -m venv ~/venvs/myproj
source ~/venvs/myproj/bin/activate
pip install "biopython==1.84"
deactivatevenv created
Python 3.13.5
Notice the venv reused the system Python 3.13.5 — it does not manage the interpreter or any C libraries, it just gives Python packages a private folder. Use venv when your project is pure Python. Use conda when you need actual bioinformatics binaries, which is almost always.
Common errors and fixes
| Error | Fix |
|---|---|
CondaToSNonInteractiveError / "Terms of Service have not been accepted" pointing at defaults | Stop using the Anaconda defaults channel. Always pass --override-channels -c conda-forge -c bioconda, or run conda config --remove channels defaults. |
PackagesNotFoundError on Apple Silicon | The package has no osx-arm64 build. Recreate under Rosetta: CONDA_SUBDIR=osx-64 conda create -n env -c conda-forge -c bioconda <pkg>, then conda config --env --set subdir osx-64. Requires softwareupdate --install-rosetta. |
| Solver is extremely slow or hangs on "Solving environment" | Set conda config --set channel_priority strict and rely on the libmamba solver (default in conda 25.x). Strict priority prevents cross-channel thrash. |
conda activate myenv fails with "CommandNotFoundError: Your shell has not been properly configured" | Run conda init zsh once (macOS default shell) and restart the terminal, or source ~/miniforge3/etc/profile.d/conda.sh first. |
Installing everything into base, causing conflicts across projects | Never install into base. Create one named env per tool: conda create -n tool .... Keep base minimal. |
| Env breaks after an update because nothing was pinned | Pin versions in the spec (samtools=1.21) and export with conda env export --from-history > env.yml. Rebuild anywhere with conda env create -f env.yml. |
| Mixing pip and conda leaves the env broken | Install everything you can via conda first; use pip only for packages not on conda, and do it last inside the activated env. Record pip deps in the pip: section of the exported YAML. |
Managing your environments
List every environment you have:
$ conda env list
# conda environments:
#
base /opt/homebrew/Caskroom/miniconda/base
bu-aligners /opt/homebrew/Caskroom/miniconda/base/envs/bu-aligners
bu-bedtools /opt/homebrew/Caskroom/miniconda/base/envs/bu-bedtools
bu-blast /opt/homebrew/Caskroom/miniconda/base/envs/bu-blast
bu-gatk4 /opt/homebrew/Caskroom/miniconda/base/envs/bu-gatk4
bu-nextflow /opt/homebrew/Caskroom/miniconda/base/envs/bu-nextflow
bu-pymol /opt/homebrew/Caskroom/miniconda/base/envs/bu-pymol
bu-rdkit /opt/homebrew/Caskroom/miniconda/base/envs/bu-rdkit
bu-salmon /opt/homebrew/Caskroom/miniconda/base/envs/bu-salmon
bu-samtools /opt/homebrew/Caskroom/miniconda/base/envs/bu-samtools
bu-scanpy /opt/homebrew/Caskroom/miniconda/base/envs/bu-scanpy
bu-snakemake /opt/homebrew/Caskroom/miniconda/base/envs/bu-snakemake
bu-vina /opt/homebrew/Caskroom/miniconda/base/envs/bu-vina
mirna /opt/homebrew/Caskroom/miniconda/base/envs/mirna
mirnap /opt/homebrew/Caskroom/miniconda/base/envs/mirnap
pymol /opt/homebrew/Caskroom/miniconda/base/envs/pymol
rtest /opt/homebrew/Caskroom/miniconda/base/envs/rtest
One line per tool — that is the pattern this whole series builds toward.
Export an environment for reproducibility. The --from-history flag records only the specs you asked for, which keeps the file portable across machines and architectures:
$ conda env export -n demoenv --from-history
name: demoenv
channels:
- defaults
dependencies:
- python=3.12
prefix: /opt/homebrew/Caskroom/miniconda/base/envs/demoenv
Notice the base's defaults still leaks into the exported channels: list. Before you share a YAML like this, edit it to read - conda-forge then - bioconda so anyone rebuilding hits the same channels you did. Rebuild anywhere with conda env create -f demoenv.yml.
Update a package inside an env without touching the rest of your system:
conda update -n bu-samtools --override-channels -c conda-forge -c bioconda samtoolsRemove an environment completely when you are done — this deletes the whole folder and reclaims the disk:
conda env remove -n bu-samtoolsBecause everything lived inside that one env, removal is clean and total. Nothing is left behind on the system.
Next steps
Now that the isolation pattern is clear, put it to work on a real tool:
- Install samtools & bcftools — the exact env you saw built above, start to finish.
- Install bwa & bowtie2 — read aligners, another env, same recipe.
Every remaining guide in the series reuses this one idea: one env per tool, conda-forge above bioconda, --override-channels to skip defaults, pinned versions, native arm64 with a Rosetta fallback when needed. Master it once and the rest is just swapping the package name.