Back to Blog
CondaBiocondaEnvironments

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.

SSSudipta SardarJuly 20, 202612 min read
Conda Environments, Channels & Isolation: The Bioinformatics Setup That Prevents Conflicts

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.

Never install tools into the 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.

You can also drop the channel globally with 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:

bash
conda config --set channel_priority strict

Strict 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.

bash
# 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 cleanly

Step 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
A key detail for Apple Silicon: the base install here is an x86_64 Miniconda (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:

bash
conda create -n bu-samtools \
  --override-channels -c conda-forge -c bioconda \
  "samtools=1.21" "bcftools=1.21" -y

3. 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:

bash
# 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_64

Because 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.

The Rosetta fallback above is doc-grounded — it was not exercised on the test machine, because samtools, bcftools, and htslib all resolved native osx-arm64 (see the real install output). Use it only when you actually hit a PackagesNotFoundError for a missing arm64 build.
A dedicated guide on GPU/CUDA isolation on Linux — where a conda env carries the exact CUDA toolkit a tool like AMBER needs, independent of the system driver — is coming later in this series.

Verify the install

After creating the samtools env, activate it and confirm the tools are present and the versions are what you expect:

bash
conda activate bu-samtools
samtools --version | head -3
bcftools --version | head -2
samtools 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:

bash
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.bam
2 + 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:

bash
python3 -m venv ~/venvs/myproj
source ~/venvs/myproj/bin/activate
pip install "biopython==1.84"
deactivate
venv 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

ErrorFix
CondaToSNonInteractiveError / "Terms of Service have not been accepted" pointing at defaultsStop using the Anaconda defaults channel. Always pass --override-channels -c conda-forge -c bioconda, or run conda config --remove channels defaults.
PackagesNotFoundError on Apple SiliconThe 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 projectsNever install into base. Create one named env per tool: conda create -n tool .... Keep base minimal.
Env breaks after an update because nothing was pinnedPin 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 brokenInstall 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:

bash
conda update -n bu-samtools --override-channels -c conda-forge -c bioconda samtools

Remove an environment completely when you are done — this deletes the whole folder and reclaims the disk:

bash
conda env remove -n bu-samtools

Because 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:

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.