# Locking an Ansible development environment: pip, venv, pip-tools, uv or an execution environment

First entry in the Ansible development environment series. An Ansible project needs a runtime on the *control node*, the machine that runs `ansible-playbook`. That runtime has five layers, and each tool below fixes some of them:

1. a Python interpreter;
2. Python packages: ansible-core, ansible-runner and what they pull in;
3. [collections](block-rescue-always-error-handling.md), installed with `ansible-galaxy`;
4. system packages, such as `openssh-clients` or a database client library;
5. the operating system underneath.

*Locking* means recording the exact version of everything in a layer, in a file, so that anyone can reinstall the same set. A *lock file* holds that record. The versions you accept, the *constraints*, live in a separate file you edit by hand.

## The choices, by maturity and by Ansible standards

"Maturity" is age, maintainer and version stability. "Ansible standards" is what Ansible's own documentation and tooling use or recommend, with the evidence in the last column.

| Choice | First release | Locks | Maturity | Ansible standards status |
|---|---|---|---|---|
| **pip** | 2008 (PyPA) | Layer 2, and only if you pin everything by hand | Highest | The install method Ansible documents: *"the officially supported means of installing the python packages with `pip`"*, next to `pipx` |
| **venv** | Python 3.3, 2012 (standard library) | Nothing: it isolates, it doesn't lock | Highest | Acknowledged, not recommended: venvs *"**partially** resolve the dependency issue"* and *"have drawbacks and natural limitations"* |
| **pip-tools** | 2012 (Jazzband), 7.x | Layer 2, for one Python version | High | Not named in the docs, but Ansible projects use it: ansible-builder's own docs lock says *"autogenerated by pip-compile"*. Its `requirements.txt` output is what ansible-builder reads |
| **Poetry** | 2018, 2.x | Layer 2, all Python versions | High | Not used by Ansible tooling. Poetry 2 dropped `export` from its core, so it needs a plugin to produce a `requirements.txt` for ansible-builder |
| **Execution environment** (ansible-builder) | 2020; current v3 format since builder 3.0, May 2023 | All five layers, in a container image | High | **The** Ansible runtime format. AWX and Red Hat's Ansible Automation Platform run jobs in EEs, and ansible-navigator runs the same image locally |
| **uv** | 2024 (Astral), 0.x | Layer 2, all Python versions; also installs the interpreter (layer 1) | Newest, pre-1.0, fast-moving | Used by Ansible's own dev tooling: `ansible-dev-environment` switches to uv whenever it's installed |

Ranked by maturity: pip, venv, pip-tools, Poetry, EE, uv. Ranked by Ansible standards: EE, pip (with pipx), then pip-tools and uv, which Ansible's own projects use, then venv, and Poetry last.

## What each one actually gives you

**pip** installs, it doesn't lock. `pip freeze > requirements.txt` snapshots whatever is installed, including leftovers, and `pip install -r requirements.txt` only reproduces it if every line is pinned. pip 26 adds `pip lock`, which writes the standard `pylock.toml` format. It prints *"pip lock is currently an experimental command"*, and `pip install -r pylock.toml` is marked experimental too.

**venv** gives each project its own interpreter directory, so two projects can run different ansible-core versions. The [uv entry](../python/newer-python-with-uv-without-touching-system-python-rhel.md) shows one. A venv records nothing: rebuild it next month and you get next month's versions. `pipx`, which Ansible's install guide covers, is a venv per application.

**pip-tools** turns a short `requirements.in` into a fully pinned `requirements.txt`. The lock is only valid for the Python it was compiled with, and its header says which one.

**Poetry** and **uv** keep constraints in `pyproject.toml` and write a lock (`poetry.lock`, `uv.lock`) covering every Python version the project allows. uv also replaces pip-tools (`uv pip compile`), exports a `requirements.txt` (`uv export`), and installs Python itself. Both are compared with pip-tools in [the next entry](pinning-ansible-core-pip-tools-uv-poetry.md), which also covers the version syntax traps specific to ansible-core.

**An execution environment** (EE) is a container image with ansible-core, ansible-runner, Python packages, collections and system packages already installed. A *container image* is a packaged filesystem that runs the same on any host with Podman or Docker. `ansible-builder` builds it from an `execution-environment.yml` file. It isn't a lock by itself: ansible-builder installs what its inputs say, so an unpinned input gives a different image on every build. It's the layer the others feed, as [the third entry](execution-environment-from-a-locked-requirements-file.md) shows.

## Which to pick

These aren't alternatives so much as layers:
- **On a laptop**, use uv or pip-tools, in a venv, to lock layer 2. Pick pip-tools if you want the most conservative tool, and uv if you want speed and a Python installer; Ansible's own tooling uses both. Poetry works, but nothing in the Ansible toolchain reads its lock directly.
- **For what runs in production**, build an EE from that same locked `requirements.txt`, with collections and the base image pinned too. On Ansible Automation Platform, an EE is how jobs run anyway.
- **Plain pip plus a venv** is fine for trying things out, but it isn't a lock. Neither is an unpinned EE.

## Sources

- Ansible docs, from [ansible/ansible-documentation](https://github.com/ansible/ansible-documentation): [installation guide](https://docs.ansible.com/projects/ansible/latest/installation_guide/intro_installation.html) (pip, pipx) and [introduction to execution environments](https://docs.ansible.com/projects/ansible/latest/getting_started_ee/introduction.html) (venv limitations, EE tooling).
- [ansible-builder](https://ansible.readthedocs.io/projects/builder/en/stable/): its 3.1.1 sdist, including `docs/requirements.txt`.
- `ansible-dev-environment` 26.9.0 source: the `--uv` option defaults to on, and logs *"uv is available and will be used instead of venv/pip"*.
- [pip `lock`](https://pip.pypa.io/en/stable/cli/pip_lock/) and the [`pylock.toml` specification](https://packaging.python.org/en/latest/specifications/pylock-toml/); [Python `venv`](https://docs.python.org/3/library/venv.html); [pipx](https://pipx.pypa.io/stable/); [Poetry CLI](https://python-poetry.org/docs/cli/).
- First-release dates are the oldest upload on PyPI. `pip lock` and `poetry export` were run with pip 26.2.1 and Poetry 2.5.1.
