Concepts¶
Overview¶
tox is an environment orchestrator. Use it to define how to set up and execute various tools on your projects. The tool can set up environments for and invoke:
For a step-by-step introduction, see the Getting Started tutorial. For practical recipes, see How-to Guides.
System overview¶
Below is a graphical representation of the tox lifecycle:
flowchart TD
start(( )) --> config[Load configuration]
config --> envloop
subgraph envloop [for each selected environment]
direction TB
create[Create environment]
create --> depmode{pylock set?}
depmode -- yes --> pylock[Install from pylock.toml]
depmode -- no --> deps[Install deps + dependency groups]
pylock --> pkg{package project?}
deps --> pkg
pkg -- yes --> build[Build and install package]
pkg -- no --> extra
build --> extra[Run extra_setup_commands]
extra --> cmds
cmds[Run commands]
end
cmds --> report[Report results]
report --> done(( ))
classDef configStyle fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e3a5f
classDef envStyle fill:#dcfce7,stroke:#22c55e,stroke-width:2px,color:#14532d
classDef pkgStyle fill:#ffedd5,stroke:#f97316,stroke-width:2px,color:#7c2d12
classDef setupStyle fill:#fde68a,stroke:#fbbf24,stroke-width:2px,color:#78350f
classDef cmdStyle fill:#ede9fe,stroke:#8b5cf6,stroke-width:2px,color:#3b0764
classDef reportStyle fill:#ccfbf1,stroke:#14b8a6,stroke-width:2px,color:#134e4a
classDef decisionStyle fill:#fef9c3,stroke:#eab308,stroke-width:2px,color:#713f12
classDef pylockStyle fill:#e0e7ff,stroke:#6366f1,stroke-width:2px,color:#312e81
class config configStyle
class create,deps envStyle
class build pkgStyle
class extra setupStyle
class cmds cmdStyle
class report reportStyle
class depmode,pkg decisionStyle
class pylock pylockStyle
The primary tox states are:
Configuration: load tox configuration files (such as
tox.toml,tox.ini,pyproject.tomlandtoxfile.py) and merge it with options from the command line plus the operating system environment variables. Configuration is loaded lazily – individual environment settings are only read when that environment is used.Environment: for each selected tox environment (e.g.
3.13,lint) do:Creation: create a fresh environment; by default virtualenv is used, but configurable via runner. For
virtualenvtox will use the virtualenv discovery logic where the python specification is defined by the tox environments base_python (if not set will try to extract it from the environment name, then fall back to default_base_python, and finally to the Python running tox). The specification can include a CPU architecture suffix (e.g.cpython3.12-64-arm64) to constrain discovery to a specific ISA — the architecture is derived fromsysconfig.get_platform()and validated after discovery. This is created at first run only to be reused at subsequent runs. If certain aspects of the project change (python version, dependencies removed, etc.), a re-creation of the environment is automatically triggered. To force the recreation tox can be invoked with the recreate flag (-r). When recreation occurs, any recreate_commands run inside the old environment before its directory is removed – this lets tools like pre-commit clean their external caches. Failures in these commands are logged as warnings but never block the recreation.Install dependencies (optional): install the environment dependencies. When pylock is set, tox installs locked dependencies from the PEP 751 lock file (filtered by extras, dependency groups, and platform markers). Otherwise, it installs deps and dependency_groups. By default
pipis used to install packages, however one can customize this viainstall_command.Packaging (optional): create a distribution of the current project (see Packaging below).
Steps 2 and 3 can be selectively skipped with CLI flags:
--skip-pkg-installskips step 3 only (packaging and package installation), while still installing dependencies.--skip-env-installskips both steps 2 and 3 entirely, reusing the environment as-is. This is useful when working offline or when the environment is already fully set up from a previous run. See Reuse an environment without network for practical usage.
Extra setup commands (optional): run the extra_setup_commands specified. These execute after all installations complete but before test commands, and run during the
--notestphase.Commands: run the specified commands in the specified order. Whenever the exit code of any of them is not zero, stop and mark the environment failed. When you start a command with a dash character, the exit code will be ignored. If commands_retry is set, failed commands are retried up to the configured number of times before being treated as a failure.
Report print out a report of outcomes for each tox environment:
____________________ summary ____________________ 3.13: commands succeeded ERROR: 3.12: commands failed
Only if all environments ran successfully tox will return exit code
0(success). In this case you’ll also see the messagecongratulations :).
Environment variable handling¶
tox takes care of environment variable isolation. By default, it removes system environment variables not specified via
pass_env and alters the PATH variable so that commands resolve within the current active tox environment.
External commands need to be explicitly allowed via allowlist_externals.
Evaluation order: tox composes the environment for command execution in this order:
pass_env – glob patterns are matched against the host
os.environ. Matching variables are passed through.disallow_pass_env – exclusion patterns are applied to remove specific variables after
pass_envexpansion.PATH – tox prepends the virtual environment’s binary directory to
PATH, so commands resolve inside the virtualenv first.set_env – values defined here are applied last and can override anything from the previous steps, including
PATH.Injected variables – tox adds
TOX_ENV_NAME,TOX_WORK_DIR,TOX_ENV_DIR,VIRTUAL_ENV,PIP_USER=0, andPYTHONIOENCODING=utf-8. These cannot be overridden.
PATH behavior: because tox prepends the virtualenv bin/ directory to PATH at step 3, commands like
python and pip resolve to the virtualenv versions. If you override PATH in set_env, be aware that this
replaces the composed PATH entirely – you should include {env_bin_dir} in your custom value to preserve
virtualenv resolution:
[env_run_base]
set_env.PATH = "{env_bin_dir}{:}/usr/local/bin{:}{env:PATH}"
[testenv]
set_env =
PATH = {env_bin_dir}{:}/usr/local/bin{:}{env:PATH}
Conditional environment variables: in INI, set_env supports PEP 508-style markers separated by ; to
conditionally set variables based on platform:
[testenv]
set_env =
COVERAGE_FILE = {work_dir}/.coverage.{env_name}
LDFLAGS = -L/usr/local/lib ; sys_platform == "darwin"
Conditional value evaluation¶
Added in version 4.40.
Changed in version 4.50: Added factor['NAME']/env['VAR'] subscript syntax and env_name variable.
TOML configurations support replace = "if" to conditionally select values at configuration load time. The
condition field accepts expressions that are parsed using Python’s ast module and evaluated against the host
os.environ.
The expression language supports:
env.VAR– resolves to the value of the environment variableVAR, or empty string if unset. An empty string is falsy, any non-empty string is truthy.env['VAR']– same asenv.VAR, useful when the variable name needs to be in a string.factor.NAME– resolves toTrueifNAMEis a factor in the environment name or platform,Falseotherwise.factor['NAME']– same asfactor.NAME, for names that aren’t valid Python identifiers. Use this for version numbers likefactor['3.14']sincefactor.3.14would be a syntax error.env_name– the full environment name as a string (e.g.,test-3.14). Use for exact name matching.'literal'– a string literal for comparison.==,!=– string equality and inequality.and,or,not– boolean combinators with standard Python precedence.
Conditions are evaluated before set_env is applied. The env.VAR lookup reads directly from os.environ, not
from the tox set_env configuration. This avoids circular dependencies – a set_env value can use replace =
"if" to check a host variable without triggering a recursive load.
Both then and else values are processed through the normal TOML replacement pipeline, so they can contain nested
substitutions like {env_name} or { replace = "env", ... }. Only the selected branch is evaluated.
For syntax details and examples, see Conditional value reference. For practical recipes, see Set values based on a condition.
Dependency change detection¶
tox discovers package dependency changes (via PEP 621 or PEP 517
prepare_metadata_for_build_wheel/build_wheel metadata). When new dependencies are added they are installed on
the next run. When a dependency is removed the entire environment is automatically recreated. This also works for
requirements files within deps. In most cases you should never need to use the --recreate flag – tox
detects changes and applies them automatically.
Lock file installation (PEP 751)¶
Added in version 4.44.
The pylock setting installs dependencies from a PEP 751 lock file (pylock.toml). It is mutually exclusive
with deps — a lock file already contains all transitive dependencies with exact versions, so mixing both sources
would create conflicts. Lock files differ from deps in that every dependency is already resolved — the file contains
exact versions, markers, and artifact URLs for every package.
Extras and dependency groups: lock files can declare extras and dependency-groups at the top level, with
per-package markers like 'docs' in extras or 'dev' in dependency_groups. Use the existing extras and
dependency_groups settings to select which groups to include — tox evaluates these markers together with platform
markers (sys_platform, python_version, etc.) against the target Python interpreter (not the host running
tox) to filter packages at install time.
How it works today: pip does not yet support installing from pylock.toml directly. tox parses the lock file
using the packaging.pylock module, evaluates markers to filter packages, transpiles matching packages to a temporary
requirements file ({env_dir}/pylock.txt), and passes it to pip with --no-deps. The --no-deps flag prevents
pip from re-resolving transitive dependencies, ensuring the exact versions from the lock file are installed.
Plugin support: the Pylock object is passed through the tox_on_install plugin hook. Installer plugins can
inspect it via isinstance(arguments, Pylock) and handle lock files natively (e.g. uv pip install --pylock)
instead of relying on the transpile path.
Future: when pip gains native pylock.toml support, the transpile step will be replaced with a direct pip
invocation. No configuration changes will be needed.
Change detection works the same as for deps: tox caches the resolved requirements. When packages are added, only
the new ones are installed. When packages are removed, the environment is recreated.
Open-ended range bounds¶
Both INI and TOML support generative environment lists with open-ended ranges. INI uses curly-brace syntax
(py3{10-}), while TOML uses range dicts ({ prefix = "py3", start = 10 }). Instead of probing the system for
available interpreters (which would be slow and environment-dependent), tox tracks the supported CPython versions via two constants:
LATEST_PYTHON_MINOR_MIN– the oldest supported CPython minor version (currently 10, for Python 3.10)LATEST_PYTHON_MINOR_MAX– the latest supported CPython minor version (currently 14, for Python 3.14)
These values are updated with each tox release. A right-open range {10-} uses LATEST_PYTHON_MINOR_MAX as its
upper bound; a left-open range {-13} uses LATEST_PYTHON_MINOR_MIN as its lower bound.
This design is deterministic and fast – the expansion happens at configuration load time with no I/O – while keeping environment lists future-proof across tox upgrades. Environments for interpreters not installed on the system are naturally skipped by the skip_missing_interpreters setting.
Environment templates (env_base)¶
tox provides three levels for configuring environments:
``env_run_base`` – global defaults inherited by all run environments. Use this when every environment shares the same setting (e.g. a common
depsorcommands).``env_base.{name}`` – named templates that generate multiple environments from factor combinations. Use this when a group of environments shares configuration but differs from other groups. The
factorskey defines which environments to generate via Cartesian product; all other keys in the section become the template’s defaults.``env.{name}`` – explicit per-environment configuration. Use this for one-off environments (like
lint) or to override specific settings in a generated environment.
The inheritance chain resolves bottom-up: env.{name} > env_base.{template} > env_run_base. A setting in
env.{name} shadows the same setting in env_base, which in turn shadows env_run_base.
Templates themselves are not runnable environments – they exist only to define shared configuration. Only the generated
environments (template name + factor suffix) appear in tox list and can be run.
For the configuration reference, see Environment base templates. For practical recipes, see Test a matrix of configurations with env_base.
Main features¶
automation of tedious Python related test activities
test your Python package against many interpreter and dependency configurations
automatic customizable (re)creation of virtualenv test environments
installs your project into each virtual environment
test-tool agnostic: runs pytest, unittest, or any other test runner
plugin system to modify tox execution with simple hooks
uses pip and virtualenv by default; plugins can replace either
cross-Python compatible: tox requires CPython 3.10 and higher, but it can create environments for older versions. Special configuration might be required: Test end-of-life Python versions.
cross-platform: Windows, macOS and Unix style environments
full interoperability with devpi: is integrated with and is used for testing in the devpi system, a versatile PyPI index server and release managing tool
concise reporting about tool invocations and configuration errors
supports using different / multiple PyPI index servers
Packaging¶
tox builds projects in a PEP 518 compatible virtual environment and communicates with the build backend according to
the interface defined in PEP 517 and PEP 660. To define package build dependencies and specify the build backend
to use, create a pyproject.toml at the root of the project. For example to use hatch:
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling>=0.22", "hatch-vcs>=0.2"]
Package modes¶
The package configuration controls how the project is packaged:
flowchart TD
pkg{package setting}
pkg -- sdist --> sdist[sdist — default]
pkg -- wheel --> wheel[wheel — faster install]
pkg -- sdist-wheel --> sdistwheel[sdist-wheel — build wheel from sdist]
pkg -- editable --> editable[editable — PEP 660]
pkg -- editable-legacy --> legacy[editable-legacy — pip -e]
pkg -- skip --> skip[skip — no packaging]
pkg -- external --> external[external — provided]
sdist --> env_pkg[build env: .pkg]
wheel --> env_wheel[build env: .pkg-cpython313]
sdistwheel --> env_pkg[sdist in: .pkg]
sdistwheel --> env_wheel2[wheel in: .pkg-cpython313]
editable --> env_wheel
legacy --> env_pkg
skip --> no_env[no build env]
external --> no_env
classDef decisionStyle fill:#fef9c3,stroke:#eab308,stroke-width:2px,color:#713f12
classDef pkgStyle fill:#ffedd5,stroke:#f97316,stroke-width:2px,color:#7c2d12
classDef envStyle fill:#dcfce7,stroke:#22c55e,stroke-width:2px,color:#14532d
classDef skipStyle fill:#f3f4f6,stroke:#9ca3af,stroke-width:2px,color:#374151
class pkg decisionStyle
class sdist,wheel,sdistwheel,editable,legacy,external pkgStyle
class env_pkg,env_wheel,env_wheel2 envStyle
class skip,no_env skipStyle
sdist(default): builds a source distributionwheel: builds a wheel (much faster to install)sdist-wheel: builds a source distribution first, then builds a wheel from that sdist (validates sdist completeness)editable: builds an editable wheel as defined by PEP 660editable-legacy: invokes pip with-e(fallback when the backend doesn’t support PEP 660)skip: skips packaging entirely (useful for tools like linters that don’t need the project installed)external: uses an externally provided package
Build environments¶
tox uses a virtual environment for building, whose name depends on the artifact type:
For source distributions: the package_env (default
.pkg)For wheels: the wheel_build_env (default
.pkg-<impl><version>, e.g..pkg-cpython313)For sdist-wheel: uses two environments — the package_env for building the sdist, and the wheel_build_env (default
.pkg-<impl><version>) for building the wheel from the extracted sdist
For pure Python projects (no C extensions), set wheel_build_env to the same value as package_env. This way the wheel is built once and reused for all tox environments:
[env_run_base]
package = "wheel"
wheel_build_env = ".pkg"
[testenv]
package = wheel
wheel_build_env = .pkg
Packaging environment configuration¶
Packaging environments do not inherit settings from env_run_base (TOML) or [testenv] (INI). Instead, they
inherit from the env_pkg_base table (TOML) or [pkgenv] section (INI). This prevents test settings from
conflicting with packaging settings.
[env_pkg_base]
pass_env = ["PKG_CONFIG", "PKG_CONFIG_PATH"]
[pkgenv]
pass_env =
PKG_CONFIG
PKG_CONFIG_PATH
To configure a specific packaging environment, use the standard environment syntax (e.g. [testenv:.pkg] in INI or
env.".pkg" in TOML).
Auto-provisioning¶
When the installed tox version does not satisfy either the requires or the min_version, tox automatically creates a virtual environment under provision_tox_env name that satisfies those constraints and delegates all calls to this meta environment.
flowchart TD
invoke[tox invoked] --> check{requires and min_version satisfied?}
check -- yes --> run[run normally]
check -- no --> create[create .tox/.tox virtualenv]
create --> install[install tox + required plugins]
install --> delegate[re-invoke tox inside .tox/.tox]
delegate --> run
classDef decisionStyle fill:#fef9c3,stroke:#eab308,stroke-width:2px,color:#713f12
classDef envStyle fill:#dcfce7,stroke:#22c55e,stroke-width:2px,color:#14532d
classDef cmdStyle fill:#ede9fe,stroke:#8b5cf6,stroke-width:2px,color:#3b0764
class check decisionStyle
class create,install envStyle
class invoke,run,delegate cmdStyle
For example given:
requires = ["tox>=4", "tox-uv>=1"]
[tox]
requires =
tox>=4
tox-uv>=1
tox will automatically ensure that both the minimum version and requires constraints are satisfied, by creating a
virtual environment under .tox, and then installing into it tox>=4 and tox-uv>=1. Afterwards all tox
invocations are forwarded to the tox installed inside .tox/.tox (referred to as the provisioned tox).
This allows tox to automatically set up itself with all its plugins for the current project. If the host tox satisfies the constraints no provisioning is done (to avoid setup cost and indirection).
Note
The provisioning environment (.tox by default) does not inherit settings from env_run_base (TOML) or
[testenv] (INI). It must be explicitly configured if you need to customize it (e.g. env.".tox" in TOML or
[testenv:.tox] in INI).
Parallel mode¶
tox allows running environments in parallel mode via the parallel sub-command:
After the packaging phase completes tox runs environments in parallel processes (multi-thread based).
The
--parallelflag takes an argument specifying the degree of parallelization, defaulting toauto:allto run all invoked environments in parallel,autoto limit it to CPU count,or pass an integer to set that limit.
Parallel mode displays a progress spinner while running environments in parallel, and reports outcome as soon as environments complete. To run without the spinner, use
--parallel-no-spinner.Parallel mode by default shows output only of failed environments and ones marked as parallel_show_output
= true.Environments can declare dependencies on other environments via depends. tox re-orders the environment list to satisfy these dependencies (also for sequential runs). In parallel mode, tox only schedules an environment once all of its dependencies have finished (independent of their outcome).
dependssupports glob patterns (*,?,[seq]) usingfnmatch, so instead of listing each environment explicitly you can writedepends = 3.*to match all environments starting with3..Warning
dependsdoes not pull in dependencies into the run target. For example, if you select3.13,3.12,coveragevia-etox will only run those three (even ifcoveragemay specify asdependsother targets too – such as3.13, 3.12, 3.11).--parallel-live/-oshows live output of stdout and stderr, and turns off the spinner.Parallel evaluation disables standard input. Use non-parallel invocation if you need standard input.
Example final output:
$ tox -e 3.13,3.12,coverage -p all
✔ OK 3.12 in 9.533 seconds
✔ OK 3.13 in 9.96 seconds
✔ OK coverage in 2.0 seconds
___________________________ summary ______________________________________________________
3.13: commands succeeded
3.12: commands succeeded
coverage: commands succeeded
congratulations :)
Example progress bar, showing a rotating spinner, the number of environments running and their list (limited up to 120 characters):
⠹ [2] 3.13 | 3.12
Fail-fast mode¶
When running multiple environments, tox normally runs all of them even if an early environment fails. The
--fail-fast flag stops execution after the first environment failure, saving time in CI pipelines and development
workflows.
tox run -e 3.13,3.12,3.11 --fail-fast
You can also enable it per-environment via the fail_fast configuration:
[env.critical]
fail_fast = true
commands = [["pytest", "tests/critical"]]
[testenv:critical]
fail_fast = true
commands = pytest tests/critical
The fail-fast behavior:
Works in both sequential and parallel execution modes.
In parallel mode, environments already running when a failure occurs will continue to completion. Only environments not yet queued will be skipped.
Respects ignore_outcome – environments with
ignore_outcome = truewill not trigger fail-fast even if they fail.Respects environment dependencies defined via depends – dependent environments will not run if a dependency fails with fail-fast enabled.
Environments not yet started are skipped with exit code -2 and marked as
SKIPin the output.The overall tox exit code will be the exit code of the first failed environment.
Configuration inheritance¶
Every tox environment has its own configuration section. If a value is not defined in the environment-specific section,
it falls back to the base section (base configuration). For tox.toml this is the env_run_base table, for
tox.ini this is [testenv]. If the base section also lacks the value, the configuration default is used.
[env_run_base]
commands = [["pytest", "tests"]]
[env.test]
description = "run the test suite with pytest"
[testenv]
commands = pytest tests
[testenv:test]
description = run the test suite with pytest
Here test inherits commands from the base because it is not specified in [env.test].
Virtualenv version pinning¶
tox creates isolated environments using virtualenv, which it imports as a library. This works well when the
installed virtualenv supports all target Python versions, but breaks down at the edges: older virtualenv releases
(pre-20.22) are required for Python 3.6 support, while the latest virtualenv is needed for Python 3.15+. Since tox can
only import one virtualenv version per process, projects that need both old and new Pythons in a single tox.toml hit
a wall.
The virtualenv_spec setting resolves this by decoupling the virtualenv used for environment creation from the one tox imports. When set, tox:
Creates a bootstrap venv (using the stdlib
venvmodule) in.tox/.virtualenv-bootstrap/.Installs the specified virtualenv version into that bootstrap venv via pip.
Runs the bootstrapped virtualenv as a subprocess instead of calling
session_via_cli()from the imported library.
The bootstrap is content-addressed by a hash of the spec string, so different specs get separate cached environments. A file lock protects against concurrent bootstrap creation (relevant in parallel mode). Once bootstrapped, subsequent runs skip directly to step 3.
When virtualenv_spec is empty (the default), tox uses the imported virtualenv with zero overhead – the subprocess
path only activates when explicitly configured. The spec is included in the environment cache key, so changing it
triggers automatic recreation.
This design mirrors tox’s own auto-provisioning mechanism (requires / min_version), where tox bootstraps itself
into a separate environment when the running installation doesn’t meet the declared requirements.
Known limitations¶
Interactive terminal programs¶
Programs that require advanced terminal control — such as IPython, debuggers with rich UIs, or any tool built on prompt_toolkit — need direct terminal access to work correctly.
By default, tox captures subprocess output by routing stdout and stderr through pseudo-terminal (PTY) pairs.
This is necessary for logging, result reporting, and colorized output. However, the subprocess’s stdin remains
connected to the real terminal. This means stdin and stdout are on different terminal devices.
Libraries like prompt_toolkit assume all streams share the same terminal. They set raw mode on stdin (to read
individual keystrokes) while writing VT100 escape sequences to stdout (for cursor positioning, screen clearing,
etc.). When stdout goes through tox’s capture buffer instead of directly to the terminal, escape sequences are
delayed and the synchronous terminal control these libraries depend on breaks.
Solution:
Use the --no-capture (or -i) flag to disable output capture and give the subprocess direct terminal access:
# Run IPython with full terminal support
tox run -e 3.13 -i -- ipython
# Run debugger interactively
tox run -e 3.13 -i -- python -m pdb script.py
This flag is mutually exclusive with --result-json and parallel mode. See Run interactive programs for
details.
Alternative workarounds if you cannot use --no-capture:
For IPython, pass
--simple-promptto disableprompt_toolkit’s advanced terminal features.For other tools, look for a “dumb terminal” or “no-color” mode that avoids VT100 escape sequences.
Debian/Ubuntu without python3-venv¶
On Debian and Ubuntu, the system Python is split into multiple packages. The python3 package does not include the
venv module or ensurepip — these are in the separate python3-venv package.
tox itself is not affected because it uses virtualenv (which bundles its own bootstrap mechanism) rather
than stdlib venv. However, tools that tox runs as commands inside environments may use stdlib venv
internally and fail. A common example is build (pyproject-build), which creates an isolated build
environment using venv when a recent pip is available.
If you see errors like:
The virtual environment was not created successfully because ensurepip is not available.
On Debian/Ubuntu systems, you need to install the python3-venv package.
this is the tool inside the tox environment hitting the missing system package, not tox itself.
Solutions:
Misplaced configuration keys¶
tox configuration is split into two sections: core ([tox] / top-level TOML) and environment ([testenv] /
env_run_base in TOML). Options placed in the wrong section are silently ignored because tox cannot distinguish a
misplaced option from a plugin-defined key that isn’t loaded yet (e.g. during provisioning).
To detect misplaced keys:
Run
tox run -v— unused keys are printed as warnings before the final report.Run
tox config— unused keys appear as# !!! unused:comments per section.Run
tox config --format jsonor--format toml— unused keys appear in an"unused"array per section.
For example, putting ignore_base_python_conflict in [testenv] instead of [tox]:
[testenv:py]
...
# !!! unused: ignore_base_python_conflict
{
"env": {
"py": {
"unused": ["ignore_base_python_conflict"]
}
}
}
[env.py]
unused = ["ignore_base_python_conflict"]
See the Core and tox environment reference sections for which options belong where.