← All TILs · ansible

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

ansible - 2026-09-29

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, 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 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, 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 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

Created 2026-09-29T21:57:13+02:00 · Edit