Workspace environments and features#

Workspace environments are named conda prefixes composed from one or more features. Features group dependencies, channels, PyPI dependencies, activation settings, platform constraints, and system requirements so a project can define several related environments in one manifest.

Environments#

multi-env demo

Each environment is installed under .conda/envs/<name>/ in your project.

[environments]
default = []
docs = { features = ["docs"] }

[environments.test]
features = ["test"]

[environments.test.dependencies]
coverage = "*"

[environments.test.pypi-dependencies]
pytest-plugin = ">=1"

[environments.test.target.win-64.dependencies]
pywin32 = "*"

An implicit default environment is created when [environments] is omitted. Every declared environment inherits the top-level default feature unless no-default-feature = true is set. Dependencies declared below [environments.<name>] are private to that environment and are merged after its shared features. Environment target dependencies are merged after its unqualified private dependencies for the selected platform.

Note

Pixi’s solve-group key is accepted in manifests for compatibility but has no effect. Conda’s solver operates on a single environment at a time and does not support cross-environment version coordination. Each environment is solved independently.

Features#

Features are composable groups of dependencies, channels, and settings. They map to [feature.<name>] tables in the manifest:

[feature.test.dependencies]
pytest = ">=8.0"
pytest-cov = ">=4.0"

[feature.docs.dependencies]
sphinx = ">=7.0"
myst-parser = ">=3.0"

When an environment includes multiple features, dependencies are merged in order. Later features override earlier ones for the same package name.

Workspace dependency inheritance#

Use [workspace.dependencies] to centralize conda specs that multiple dependency tables should share. A table entry opts in explicitly with { workspace = true }. Pixi added the same workspace dependency inheritance syntax in 0.70.0, and conda-workspaces reads it from conda.toml, pixi.toml, and supported pyproject.toml tables.

[workspace.dependencies]
numpy = "1.*"
cmake = { version = ">=3.28", channel = "conda-forge" }

[dependencies]
numpy = { workspace = true }

[feature.build.dependencies]
cmake = { workspace = true, build = "h*" }

The root spec supplies the version and any other base match fields. The consuming entry may add non-version fields such as build, channel, or subdir. Restating version alongside workspace = true is an error.

Dependency mutation locations#

conda workspace add, conda workspace update, and conda workspace remove change one explicit declaration location. They do not search the composed environment for the declaration that currently wins. The dependency mutation rules define the selector mapping, workspace inheritance behavior, and wrong-location diagnostics.

# Shared feature declaration for every platform
conda workspace add --feature test pytest

# Platform override within that feature
conda workspace add --feature test --platform win-64 "pytest<9"

# Private platform override within one environment
conda workspace add --environment test --platform win-64 pywin32

workspace update uses the same selectors. A bare package name keeps the declaration unchanged and updates only that constrained root in installed prefixes and conda.lock.

Channels#

Channels are specified at the workspace level and can be overridden per feature:

[workspace]
channels = ["conda-forge"]

[feature.special.dependencies]
some-pkg = "*"

[feature.special]
channels = ["conda-forge", "bioconda"]

Feature channels are appended after workspace channels, with duplicates removed.

Platform targeting#

multi-platform demo

Per-platform dependency overrides use [target.<platform>] tables:

[dependencies]
python = ">=3.10"

[target.linux-64.dependencies]
linux-headers = ">=5.10"

[target.osx-arm64.dependencies]
llvm-openmp = ">=14.0"

[feature.test.target.win-64.dependencies]
pytest = "<9"

[environments.test.target.win-64.dependencies]
pywin32 = "*"

Each platform override is merged on top of the unqualified dependencies owned by the same default feature, named feature, or environment. An environment’s target dependencies are the last dependency layer for the selected platform.

Known vs. declared platforms#

Added in version 0.4.0: conda workspace info surfaces the reachable platform set as a known_platforms JSON key (and a matching Known Platforms row in the text view whenever a feature broadens the workspace-level set). conda workspace lock --platform <subdir> --output <fragment> validates against this same set.

The workspace-level platforms list is the default set every environment can be solved for. Individual features may declare additional platforms, and those are reachable through any environment that activates that feature. To see the full reachable set, run:

conda workspace info            # text view, extra "Known Platforms" row
conda workspace info --json     # JSON "known_platforms" key

conda workspace lock --platform <subdir> --output <fragment> validates against this reachable set, so typos like lixux-64 are rejected before the solver runs.

PyPI dependencies#

PyPI dependencies are specified separately from conda dependencies:

[pypi-dependencies]
my-local-pkg = { path = ".", editable = true }
some-pypi-only = ">=1.0"

[feature.test.pypi-dependencies]
pytest-benchmark = ">=4.0"

PyPI package names are translated to their conda equivalents via the grayskull mapping and merged into the same solver call as conda dependencies. conda-pypi delegates to the configured solver backend to resolve conda and PyPI packages together in a single pass and handles .whl installation.

To use PyPI dependencies you need:

  • conda-pypi (>=0.9.0) for name mapping and wheel extraction

  • conda-rattler-solver as the solver backend (no longer a hard dependency of conda-pypi, so install it explicitly)

  • The conda-pypi channel (conda config --append channels conda-pypi) which serves pure Python packages from PyPI as conda packages using sharded repodata (requires the rattler solver)

Local path dependencies (e.g. path = ".") are handled separately via conda-pypi’s build system after the main solve completes. Git and URL dependencies are parsed for pixi manifest compatibility but are not installed yet. conda workspace install skips them with a warning. If conda-pypi is not installed, version-only PyPI dependencies are skipped with a warning, while path dependencies fail with an installation error.

See the PyPI dependencies tutorial for a full walkthrough including editable installs and troubleshooting.

No-default-feature#

An environment can opt out of inheriting the default feature:

[environments]
minimal = { features = ["minimal"], no-default-feature = true }

This is useful for environments that need a completely independent dependency set.

Activation#

Features can specify activation scripts and environment variables:

[activation]
scripts = ["scripts/activate.sh"]
env = { MY_VAR = "value" }

[feature.dev.activation]
env = { DEBUG = "1" }

Activation settings are merged across features when composing an environment. After conda workspace install, environment variables are written to the prefix state file (available via conda activate) and activation scripts are copied to $PREFIX/etc/conda/activate.d/. Activation state and script destinations must be regular prefix-owned paths. Replace linked conda-meta/state, etc/conda, or activate.d entries before installing the environment.

Prefix generation checks reject links and replacements that remain visible at mutation boundaries. Conda and conda-pypi still receive filesystem paths, so these checks are not a sandbox against another process running as the same operating-system user that swaps and restores a prefix during one downstream call.

System requirements#

System requirements declare minimum system-level dependencies:

[system-requirements]
cuda = "12"
glibc = "2.17"

System requirements are added as virtual package constraints (__cuda >=12, __glibc >=2.17) during environment solving. This ensures the solver only picks packages compatible with the declared system capabilities.

Channel priority#

The workspace-level channel-priority setting overrides conda’s global channel priority during solving:

[workspace]
channels = ["conda-forge"]
channel-priority = "strict"

Valid values are strict, flexible, and disabled. When not set, conda’s default channel priority applies.