How-to Guides¶
Practical recipes for common tox tasks. Each section answers a specific “How do I…?” question.
Quick reference¶
Common commands¶
Each tox subcommand has a 1 (or 2) letter shortcut, e.g.
tox run=tox r,tox config=tox c.Run all default environments:
tox(runs everything in env_list).Run a specific environment:
tox run -e 3.13.Run multiple environments:
tox run -e lint,3.13(sequential, in order).Run environments in parallel:
tox parallel -e 3.13,3.12(see Parallel mode).Run all environments matching a label:
tox run -m test(see labels).Run all environments matching a factor:
tox run -f django(runs all envs containing thedjangofactor).Inspect configuration:
tox config -e 3.13 -k pass_env.Force recreation:
tox run -e 3.13 -r.
Environment variables¶
View environment variables:
tox c -e 3.13 -k set_env pass_env.Pass through system environment variables: use pass_env.
Set environment variables: use set_env.
Setup commands: commands_pre. Teardown commands: commands_post.
Change working directory: change_dir (affects install commands too if using relative paths).
Logging¶
tox logs command invocations inside .tox/<env_name>/log. Environment variables with names containing sensitive words
(access, api, auth, client, cred, key, passwd, password, private, pwd,
secret, token) are logged with their values redacted to prevent accidental secret leaking in CI/CD environments.
Test with pytest¶
A typical pytest configuration:
env_list = ["3.13", "3.12"]
[env_run_base]
deps = ["pytest>=8"]
commands = [["pytest", { replace = "posargs", default = ["tests"], extend = true }]]
[tox]
env_list = 3.13, 3.12
[testenv]
deps = pytest>=8
commands = pytest {posargs:tests}
When running tox in parallel mode, ensure each pytest invocation is fully isolated by setting a unique temporary directory:
[env_run_base]
commands = [["pytest", "--basetemp={env_tmp_dir}", { replace = "posargs", default = ["tests"], extend = true }]]
[testenv]
commands = pytest --basetemp="{env_tmp_dir}" {posargs:tests}
Collect coverage across multiple environments¶
A common pattern is running tests across several Python versions and combining coverage results. Use depends to ensure coverage runs after all test environments:
env_list = ["3.13", "3.12", "coverage"]
[env_run_base]
deps = ["pytest", "coverage[toml]"]
commands = [["coverage", "run", "-p", "-m", "pytest", "tests"]]
[env.coverage]
skip_install = true
deps = ["coverage[toml]"]
depends = ["3.*"]
commands = [
["coverage", "combine"],
["coverage", "report", "--fail-under=80"],
]
[tox]
env_list = 3.13, 3.12, coverage
[testenv]
deps =
pytest
coverage[toml]
commands = coverage run -p -m pytest tests
[testenv:coverage]
skip_install = true
deps = coverage[toml]
depends = 3.*
commands =
coverage combine
coverage report --fail-under=80
The -p flag (parallel mode) creates separate .coverage.<hash> files per environment. coverage combine merges
them before generating the report.
Run a one-off command (tox exec)¶
The tox exec subcommand runs an arbitrary command inside a tox environment without executing the configured
commands, commands_pre, or commands_post. It also skips package installation. Pass the command after --:
# Open a Python shell inside the "3.13" environment
tox exec -e 3.13 -- python
# Check installed packages
tox exec -e 3.13 -- pip list
# Run a script with the environment's Python
tox exec -e 3.13 -- python scripts/migrate.py --dry-run
The command must be in the environment’s PATH or listed in allowlist_externals. tox exec is useful for
debugging, running one-off scripts, or interactively exploring an environment without modifying your configuration.
Override configuration defaults¶
tox provides several ways to override configuration values without editing the configuration file.
User-level configuration file: tox reads a user-level config file whose location is shown in tox --help. The
location can be changed via the TOX_CONFIG_FILE environment variable.
Environment variables: Any tox setting can be set via an environment variable with the TOX_ prefix:
# Use wheel packaging
TOX_PACKAGE=wheel tox run -e 3.13
CLI override: The -x (or --override) flag overrides any configuration value:
# Force editable install for a specific environment
tox run -e 3.13 -x "testenv:3.13.package=editable"
Use labels to group environments¶
Labels let you assign tags to environments and run them as a group with tox run -m <label>:
env_list = ["3.13", "3.12", "lint", "type"]
[env_run_base]
labels = ["test"]
commands = [["pytest", "tests"]]
[env.lint]
labels = ["check"]
skip_install = true
deps = ["ruff"]
commands = [["ruff", "check", "."]]
[env.type]
labels = ["check"]
deps = ["mypy"]
commands = [["mypy", "src"]]
[tox]
env_list = 3.13, 3.12, lint, type
[testenv]
labels = test
commands = pytest tests
[testenv:lint]
labels = check
skip_install = true
deps = ruff
commands = ruff check .
[testenv:type]
labels = check
deps = mypy
commands = mypy src
# Run all environments labeled "check"
tox run -m check
# Run all environments labeled "test"
tox run -m test
Disallow unlisted environments¶
By default, running tox -e <name> with an environment name not defined in the configuration still works – tox
creates an environment with default settings. This can mask typos.
For example, given:
[env.unit]
deps = ["pytest"]
commands = [["pytest"]]
[testenv:unit]
deps = pytest
commands = pytest
Running tox -e unt or tox -e unti would succeed without running any tests. An exception is made for environments
that look like Python version specifiers – tox -e 3.13 or tox -e py313 would still work as intended.
Configure platform-specific settings¶
Use conditional factors to run different commands or dependencies per platform:
env_list = ["3.13-lin", "3.13-mac", "3.13-win"]
[env_run_base]
commands = [["python", "-c", "print('hello')"]]
[tox]
env_list = py{313}-{lin,mac,win}
[testenv]
platform = lin: linux
mac: darwin
win: win32
deps = lin,mac: platformdirs==3
win: platformdirs==2
commands =
lin: python -c 'print("Hello, Linus!")'
mac: python -c 'print("Hello, Tim!")'
win: python -c 'print("Hello, Satya!")'
The platform setting accepts a regular expression matched against sys.platform. If it does not match, the
entire environment is skipped. Conditional factors (lin:, mac:, win:) filter individual settings.
Note
Conditional factors and generative environments are currently only supported in the INI format (see
TOML feature gaps). For TOML, use replace = "if" with a condition expression to achieve similar results
(see Conditional value reference).
Set values based on a condition¶
Added in version 4.40.
TOML configurations can conditionally select values based on environment variables using replace = "if". The
condition field accepts expressions with env.VAR lookups, ==/!= comparisons, and and/or/not
boolean logic.
Set a variable depending on whether you are in CI:
[env_run_base]
set_env.MATURITY = { replace = "if", condition = "env.CI", then = "release", "else" = "dev" }
Add verbose flags to commands when a DEBUG variable is set:
[env_run_base]
commands = [["pytest", { replace = "if", condition = "env.DEBUG", then = ["-vv", "--tb=long"], "else" = [], extend = true }]]
Combine multiple conditions:
[env.deploy]
commands = [["deploy", { replace = "if", condition = "env.CI and env.TAG_NAME != ''", then = ["--production"], "else" = ["--dry-run"], extend = true }]]
For the full expression syntax and more examples, see Conditional value reference.
Handle env names that match subcommands¶
tox has built-in subcommands (run, list, config, etc.). If you have an environment name that matches a
subcommand, use the run subcommand explicitly:
# This would be interpreted as "tox list", not "run the list environment"
# tox -e list # does NOT work as expected
# Use the run subcommand explicitly
tox run -e list
# Or the short alias
tox r -e list
Use a custom PyPI server¶
By default tox uses pip to install Python dependencies. To change the index server, configure pip directly via environment variables:
[env_run_base]
set_env = { PIP_INDEX_URL = "https://my.pypi.example/simple" }
To allow the user to override the index server (e.g. for offline use), use substitution with a default:
[env_run_base]
set_env = { PIP_INDEX_URL = { replace = "env", name = "PIP_INDEX_URL", default = "https://my.pypi.example/simple" } }
[testenv]
set_env =
PIP_INDEX_URL = https://my.pypi.example/simple
To allow the user to override the index server (e.g. for offline use), use substitution with a default:
[testenv]
set_env =
PIP_INDEX_URL = {env:PIP_INDEX_URL:https://my.pypi.example/simple}
Use multiple PyPI servers¶
When not all dependencies are found on a single index, use PIP_EXTRA_INDEX_URL:
[env_run_base]
set_env.PIP_INDEX_URL = { replace = "env", name = "PIP_INDEX_URL", default = "https://primary.example/simple" }
set_env.PIP_EXTRA_INDEX_URL = { replace = "env", name = "PIP_EXTRA_INDEX_URL", default = "https://secondary.example/simple" }
[testenv]
set_env =
PIP_INDEX_URL = {env:PIP_INDEX_URL:https://primary.example/simple}
PIP_EXTRA_INDEX_URL = {env:PIP_EXTRA_INDEX_URL:https://secondary.example/simple}
If the index defined under PIP_INDEX_URL does not contain a package, pip will attempt to resolve it from
PIP_EXTRA_INDEX_URL.
Warning
Using an extra PyPI index for installing private packages may cause security issues. If package1 is registered
with the default PyPI index, pip will install package1 from the default PyPI index, not from the extra one.
Use constraint files¶
Constraint files define version constraints for dependencies without specifying what to install. When creating a test environment, tox invokes pip multiple times:
If deps is specified, it installs those dependencies first.
If the environment has a package (not package
skipor skip_installtrue), it:Installs the package dependencies.
Installs the package itself.
When constrain_package_deps = true is set, {env_dir}/constraints.txt is generated during install_deps based
on the specifications in deps. These constraints are then passed to pip during install_package_deps, raising an
error when package dependencies conflict with test dependencies.
For stronger guarantees, set use_frozen_constraints = true to generate constraints from the exact installed versions
(via pip freeze). This catches incompatibilities with any previously installed dependency.
Note
Constraint files are a subset of requirement files. You can pass a constraint file wherever a requirement file is accepted.
Use extras¶
If your package defines optional dependency groups (extras) in pyproject.toml, you can install them in tox
environments via the extras configuration:
# pyproject.toml
[project.optional-dependencies]
testing = ["pytest>=8", "coverage"]
docs = ["sphinx>=7"]
# tox.toml
[env_run_base]
extras = ["testing"]
[env.docs]
extras = ["docs"]
commands = [["sphinx-build", "-W", "docs", "docs/_build/html"]]
[testenv]
extras = testing
[testenv:docs]
extras = docs
commands = sphinx-build -W docs docs/_build/html
This installs your package together with the specified extras, avoiding the need to duplicate dependency lists in both
pyproject.toml and your tox configuration.
Customize virtualenv creation¶
tox uses virtualenv to create Python virtual environments. Customize virtualenv behavior through environment variables:
[env_run_base]
set_env.VIRTUALENV_PIP = "22.1"
set_env.VIRTUALENV_SYSTEM_SITE_PACKAGES = "true"
[testenv]
set_env =
VIRTUALENV_PIP = 22.1
VIRTUALENV_SYSTEM_SITE_PACKAGES = true
Any CLI flag for virtualenv can be set as an environment variable with the VIRTUALENV_ prefix (in uppercase).
Consult the virtualenv documentation for supported values.
Ignore command exit codes¶
When multiple commands are defined in commands, tox runs them sequentially and stops at the first failure
(non-zero exit code). To ignore the exit code of a specific command, prefix it with -:
[env_run_base]
commands = [
["-", "python", "-c", "import sys; sys.exit(1)"],
["python", "--version"],
]
[testenv]
commands =
- python -c 'import sys; sys.exit(1)'
python --version
To invert the exit code (fail if the command returns 0, succeed otherwise), use the ! prefix:
[env_run_base]
commands = [
["!", "python", "-c", "import sys; sys.exit(1)"],
["python", "--version"],
]
[testenv]
commands =
! python -c 'import sys; sys.exit(1)'
python --version
Retry flaky commands¶
Commands that fail due to transient errors (network timeouts, flaky tests) can be automatically retried using
commands_retry. The value specifies how many times to retry a failed command – a value of 2 means each
command is attempted up to 3 times total. Retries apply to commands_pre, commands, and
commands_post. Commands prefixed with - (ignore exit code) are never retried.
[env.integration]
description = "run integration tests with retries for flaky network calls"
commands_retry = 2
commands = [["pytest", "tests/integration"]]
[testenv:integration]
description = run integration tests with retries for flaky network calls
commands_retry = 2
commands = pytest tests/integration
Control color output¶
tox uses colored output by default. To disable it, use any of these methods:
# Via environment variable
NO_COLOR=1 tox run
# Via TERM
TERM=dumb tox run
# Via CLI flag
tox run --colored no
Use tox in CI/CD pipelines¶
tox works well in continuous integration systems. We recommend installing tox via uv for significantly faster setup times. Adding tox-uv also replaces pip with uv inside tox environments, speeding up dependency installation.
GitHub Actions:
# .github/workflows/tests.yml
name: tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: astral-sh/setup-uv@v5
- run: uv tool install tox --with tox-uv
- run: tox run -e ${{ matrix.python-version }}
GitLab CI:
# .gitlab-ci.yml
test:
image: python:3.13
before_script:
- curl -LsSf https://astral.sh/uv/install.sh | sh
- uv tool install tox --with tox-uv
script:
- tox run -e 3.13
Build documentation with Sphinx¶
Orchestrate Sphinx documentation builds with tox to integrate them into CI:
[env.docs]
description = "build documentation"
deps = ["sphinx>=7"]
commands = [
["sphinx-build", "-d", "{env_tmp_dir}/doctree", "docs", "{work_dir}/docs_out", "--color", "-b", "html"],
["python", "-c", "print(f'documentation available under file://{work_dir}/docs_out/index.html')"],
]
[testenv:docs]
description = build documentation
deps =
sphinx>=7
commands =
sphinx-build -d "{envtmpdir}{/}doctree" docs "{toxworkdir}{/}docs_out" --color -b html
python -c 'print(r"documentation available under file://{toxworkdir}{/}docs_out{/}index.html")'
This approach avoids the platform-specific Makefile generated by Sphinx and works cross-platform.
Build documentation with mkdocs¶
Define separate environments for developing and deploying mkdocs documentation:
[env.docs]
description = "run a development server for documentation"
deps = [
"mkdocs>=1.3",
"mkdocs-material",
]
commands = [
["mkdocs", "build", "--clean"],
["python", "-c", "print('###### Starting local server. Press Control+C to stop ######')"],
["mkdocs", "serve", "-a", "localhost:8080"],
]
[env.docs-deploy]
description = "build and deploy documentation"
deps = [
"mkdocs>=1.3",
"mkdocs-material",
]
commands = [["mkdocs", "gh-deploy", "--clean"]]
[testenv:docs]
description = Run a development server for working on documentation
deps =
mkdocs>=1.3
mkdocs-material
commands =
mkdocs build --clean
python -c 'print("###### Starting local server. Press Control+C to stop server ######")'
mkdocs serve -a localhost:8080
[testenv:docs-deploy]
description = built fresh docs and deploy them
deps = {[testenv:docs]deps}
commands = mkdocs gh-deploy --clean
Debug a failing tox environment¶
When an environment fails, use these techniques to investigate:
Increase verbosity to see detailed command output:
tox run -e 3.13 -vv
Inspect resolved configuration to verify settings are what you expect:
# Show all configuration for an environment tox config -e 3.13 # Show specific keys tox config -e 3.13 -k deps commands pass_env set_env
Check log files in
.tox/<env_name>/log/for full command output with timestamps.Run a command interactively inside the environment:
tox exec -e 3.13 -- python tox exec -e 3.13 -- pip list
Force recreation if you suspect a stale environment:
tox run -e 3.13 -r
Access full logs¶
tox logs command invocations inside .tox/<env_name>/log. Each execution is recorded in a file named
<index>-<run_name>.log, containing the command, environment variables, working directory, exit code, and output.
Environment variables with names containing sensitive words (access, api, auth, client, cred,
key, passwd, password, private, pwd, secret, token) are logged with their values redacted to
prevent accidental secret leaking.
Understand InvocationError exit codes¶
When a command executed by tox fails, an InvocationError is raised:
ERROR: InvocationError for command
'<command defined in tox config>' (exited with code 1)
Always check the documentation for the command that failed. For example, for pytest, see the pytest exit codes.
On Unix systems, exit codes larger than 128 indicate a fatal signal. tox provides a hint in these cases:
ERROR: InvocationError for command
'<command>' (exited with code 139)
Note: this might indicate a fatal error signal (139 - 128 = 11: SIGSEGV)
Signal numbers are documented in the signal man page.
Test end-of-life Python versions¶
tox uses virtualenv under the hood. Newer virtualenv versions drop support for older Python interpreters:
virtualenv 20.22.0 dropped Python 3.6 and earlier
virtualenv 20.27.0 dropped Python 3.7
To test against these versions, pin virtualenv:
requires = ["virtualenv<20.22.0"]
[tox]
requires = virtualenv<20.22.0
Use tox with different build backends¶
tox works with any PEP 517/PEP 518 compliant build backend. Configure the backend in pyproject.toml:
Hatchling (hatch):
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Flit:
[build-system]
requires = ["flit_core>=3.4"]
build-backend = "flit_core.buildapi"
PDM:
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
tox automatically detects and uses whatever backend is specified in [build-system]. No additional tox configuration
is needed. For build backends that need extra configuration during the build, use config_settings_build_wheel and
related options.
Migrate from tox.ini to tox.toml¶
TOML is the recommended configuration format for new projects. Here is how common INI patterns translate to TOML:
Basic structure:
# tox.toml - values at root level are core settings
requires = ["tox>=4.20"]
env_list = ["3.13", "3.12", "lint"]
# base settings for run environments
[env_run_base]
deps = ["pytest>=8"]
commands = [["pytest", "tests"]]
# environment-specific overrides
[env.lint]
skip_install = true
deps = ["ruff"]
commands = [["ruff", "check", "."]]
[tox]
requires = tox>=4.20
env_list = 3.13, 3.12, lint
[testenv]
deps = pytest>=8
commands = pytest tests
[testenv:lint]
skip_install = true
deps = ruff
commands = ruff check .
Key differences:
Strings must be quoted in TOML:
description = "run tests"vsdescription = run testsLists use JSON syntax:
deps = ["pytest", "ruff"]vs multi-linedeps = \n pytest \n ruffCommands are list-of-lists:
commands = [["pytest", "tests"]]vscommands = pytest testsPositional arguments use replacement objects:
{ replace = "posargs", default = ["tests"] }vs{posargs:tests}Environment variables in
set_envuse{ replace = "env", name = "VAR" }vs{env:VAR}Section references use
{ replace = "ref", ... }vs{[section]key}No conditional factors or generative environment lists in TOML (see TOML feature gaps)
Format your tox configuration files¶
Consistent formatting makes configuration files easier to read and review. The tox-dev organization maintains opinionated formatters for both TOML and INI configurations, available as pre-commit hooks or standalone CLI tools.
Use tox-toml-fmt for tox.toml or TOML-based configuration in pyproject.toml. It standardizes
quoting, array formatting, and table organization:
# .pre-commit-config.yaml
- repo: https://github.com/tox-dev/toml-fmt
rev: "1.6.0"
hooks:
- id: tox-toml-fmt
Also available as a standalone command via pipx install tox-toml-fmt.
Use tox-ini-fmt for tox.ini files. It normalizes boolean fields, orders sections consistently, and
formats multi-line values with uniform indentation:
# .pre-commit-config.yaml
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: "1.7.1"
hooks:
- id: tox-ini-fmt
Also available as a standalone command via pipx install tox-ini-fmt.