Skip to content

Tutorial: Using multiple environments#

In this tutorial we will show you how to use multiple environments in one pixi project.

Why is this useful?#

When developing a project you often need different tools, libraries or test environments. With pixi you can define multiple environments in one project and switch between them easily. A developer often needs all the tools they can get, whereas your testing infrastructure might not require all those tools, and your production environment might require even less. Setting up different environments for these different use cases can be a hassle, but with pixi it's easy.

Glossary#

This tutorial possibly uses some new terms, here is a quick overview:

Feature#

A feature defines a part of an environment, but are not useful without being part of an environment. You can define multiple features in one project. A feature can contain tasks, dependencies, platforms, channels and more. You can mix multiple features to create an environment. Features are defined by adding [feature.<name>.*] to a table in the manifest file.

Environment#

An environment is a collection of features. Environments can actually be installed and activated to run tasks in. You can define multiple environments in one project. Defining environments is done by adding them to the [environments] table in the manifest file.

Default#

Instead of specifying [feature.<name>.dependencies], one can populate [dependencies] directly. These top level table, are added to the "default" feature, which is added to every environment, unless you specifically opt-out.

Let's get started#

We'll simply start with a new project, you can skip this step if you already have a pixi project.

pixi init project
cd project
pixi add python

Now we have a new pixi project with the following structure:

├── .pixi
│   └── envs
│       └── default
├── pixi.lock
└── pixi.toml

Note the .pixi/envs/default directory, this is where the default environment is stored. If no environment is specified, pixi will create or use the default environment.

Adding a feature#

Let's start adding a simple test feature to our project. We can do this through the command line, or by editing the pixi.toml file. Here we will use the command line, and add a pytest dependency to the test feature in our project.

pixi add --feature test pytest
This will add the following to our pixi.toml file:
[feature.test.dependencies]
pytest = "*"
This table acts exactly the same as a normal dependencies table, but it is only used when the test feature is part of an environment.

Adding an environment#

We will add the test environment to our project to add some testing tools. We can do this through the command line, or by editing the pixi.toml file. Here we will use the command line:

pixi project environment add test --feature test
This will add the following to our pixi.toml file:
[environments]
test = ["test"]

Running a task#

We can now run a task in our new environment.

pixi run --environment test pytest --version
This has created the test environment, and run the pytest --version command in it. You can see the environment will be added to the .pixi/envs directory.
├── .pixi
   └── envs
       ├── default
       └── test
If you want to see the environment, you can use the pixi list command.
pixi list --environment test

If you have special test commands that always fit with the test environment you can add them to the test feature.

# Adding the 'test' task to the 'test' feature and setting it to run `pytest`
pixi task add test --feature test pytest
This will add the following to our pixi.toml file:
[feature.test.tasks]
test = "pytest"
Now you don't have to specify the environment when running the test command.
pixi run test
In this example similar to running pixi run --environment test pytest

This works as long as there is only one of the environments that has the test task.

Using multiple environments to test multiple versions of a package#

In this example we will use multiple environments to test a package against multiple versions of Python. This is a common use-case when developing a python library. This workflow can be translated to any setup where you want to have multiple environments to test against a different dependency setups.

For this example we assume you have run the commands in the previous example, and have a project with a test environment. To allow python being flexible in the new environments we need to set it to a more flexible version e.g. *.

pixi add "python=*"

We will start by setting up two features, py311 and py312.

pixi add --feature py311 python=3.11
pixi add --feature py312 python=3.12

We'll add the test and Python features to the corresponding environments.

pixi project environment add test-py311 --feature py311 --feature test
pixi project environment add test-py312 --feature py312 --feature test

This should result in adding the following to the pixi.toml:

[feature.py311.dependencies]
python = "3.11.*"

[feature.py312.dependencies]
python = "3.12.*"

[environments]
test-py311 = ["py311", "test"]
test-py312 = ["py312", "test"]

Now we can run the test command in both environments.

pixi run --environment test-py311 test
pixi run --environment test-py312 test
# Or using the task directly, which will spawn a dialog to select the environment of choice
pixi run test

These could now run in CI to test separate environments:

.github/workflows/test.yml
test:
  runs-on: ubuntu-latest
  strategy:
    matrix:
      environment: [test-py311, test-py312]
  steps:
  - uses: actions/checkout@v4
  - uses: prefix-dev/setup-pixi@v0
    with:
      environments: ${{ matrix.environment }}
  - run: pixi run -e ${{ matrix.environment }} test
More info on that in the GitHub actions documentation.

Development, Testing, Production environments#

This assumes a clean project, so if you have been following along, you might want to start a new project.

pixi init production_project
cd production_project

Like before we'll start with creating multiple features.

pixi add numpy python # default feature
pixi add --feature dev jupyterlab
pixi add --feature test pytest

Now we'll add the environments. To accommodate the different use-cases we'll add a production, test and default environment.

  • The production environment will only have the default feature, as that is the bare minimum for the project to run.
  • The test environment will have the test and the default features, as we want to test the project and require the testing tools.
  • The default environment will have the dev and test features.

We make this the default environment as it will be the easiest to run locally, as it avoids the need to specify the environment when running tasks.

We'll also add the solve-group prod to the environments, this will make sure that the dependencies are solved as if they were in the same environment. This will result in the production environment having the exact same versions of the dependencies as the default and test environment. This way we can be sure that the project will run in the same way in all environments.

pixi project environment add production --solve-group prod
pixi project environment add test --feature test --solve-group prod
# --force is used to overwrite the default environment
pixi project environment add default --feature dev --feature test --solve-group prod --force

If we run pixi list -x for the environments we can see that the different environments have the exact same dependency versions.

# Default environment
Package     Version  Build               Size       Kind   Source
jupyterlab  4.3.4    pyhd8ed1ab_0        6.9 MiB    conda  jupyterlab
numpy       2.2.1    py313ha4a2180_0     6.2 MiB    conda  numpy
pytest      8.3.4    pyhd8ed1ab_1        253.1 KiB  conda  pytest
python      3.13.1   h4f43103_105_cp313  12.3 MiB   conda  python

Environment: test
Package  Version  Build               Size       Kind   Source
numpy    2.2.1    py313ha4a2180_0     6.2 MiB    conda  numpy
pytest   8.3.4    pyhd8ed1ab_1        253.1 KiB  conda  pytest
python   3.13.1   h4f43103_105_cp313  12.3 MiB   conda  python

Environment: production
Package  Version  Build               Size      Kind   Source
numpy    2.2.1    py313ha4a2180_0     6.2 MiB   conda  numpy
python   3.13.1   h4f43103_105_cp313  12.3 MiB  conda  python

Non default environments#

When you want to have an environment that doesn't have the default feature, you can use the --no-default-feature flag. This will result in the environment not having the default feature, and only the features you specify.

A common use-case of this would be having an environment that can generate your documentation.

Let's add the mkdocs dependency to the docs feature.

pixi add --feature docs mkdocs

Now we can add the docs environment without the default feature.

pixi project environment add docs --feature docs --no-default-feature

If we run pixi list -x -e docs we can see that it only has the mkdocs dependency.

Environment: docs
Package  Version  Build         Size     Kind   Source
mkdocs   1.6.1    pyhd8ed1ab_1  3.4 MiB  conda  mkdocs

Conclusion#

The multiple environment feature is extremely powerful and can be used in many different ways. There is much more to explore in the reference and advanced sections. If there are any questions, or you know how to improve this tutorial, feel free to reach out to us on GitHub.