AnsibleIII · Installing and Designing the ControllerInstalling the controller
Installing: pipx, virtualenv, pip and the distro package
What you'll learn
- Compare the four install paths on binding, upgradability, coexistence and reproducibility
- Install ansible-core into an explicit virtualenv and confirm which one is in use
- State honestly what goes wrong with a system-wide pip install
- Choose an install method for a workstation and for a shared or CI controller, and justify each
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
There are four ways to put Ansible on a machine, and the tutorials that list them usually rank them by convenience. Convenience is the wrong axis. What decides whether an install is a problem in eighteen months is:
- Which Python does it bind to — and can that Python change under you?
- Can it be upgraded without touching the system — or does an Ansible upgrade become an operating-system event?
- Can two versions coexist — because upgrading safely means running both for a while.
- Is the result reproducible on another machine — from a file in the repository, not from memory.
The four paths
| Distro package | System-wide pip | pipx | Explicit venv | |
|---|---|---|---|---|
| Binds to | System Python | System Python | An interpreter pipx picks, isolated | The interpreter you name |
| Version you get | Whatever the release froze | Newest that installs | Any, you choose | Any, you pin |
| Upgrade touches the system | Yes | Yes | No | No |
| Two versions coexist | No | No | With --suffix | Yes, trivially |
| Reproducible from a file | Only with the same OS release | Poorly | Reasonably | Yes |
| Sensible for | A throwaway box | Nothing | A workstation | A shared or CI controller |
The rest of this lesson is that table, argued.
The distribution package
sudo apt install ansible-core # Debian/Ubuntu
sudo dnf install ansible-core # RHEL/Rocky/AlmaIt works, it is signed, it is patched by the distribution’s security process, and it needs no Python knowledge at all. For a throwaway machine, or for a host where Ansible is a convenience rather than the tool that changes production, this is fine.
Its problem is the previous lesson’s problem: the version is a property of your operating system release, not of what you chose. You cannot pin it to what your repository declares, you cannot install two versions to test an upgrade, and upgrading it means upgrading the distribution.
System-wide pip install ansible — what actually goes wrong
This is the instruction in every old blog post, and it deserves a straight answer rather than a dismissal.
sudo pip install ansible # refused on a PEP 668 system
sudo pip install --break-system-packages ansible # and this is whyThree concrete things go wrong.
It shares a dependency tree with the operating system. Distribution
tooling is written in Python and imports the same site-packages
directory. Ansible pulls in jinja2, cryptography, pyyaml,
packaging and resolvelib, and pip will upgrade whichever of those it
needs. When the version pip installs is not the version the distribution
built its own tools against, the breakage lands somewhere unrelated —
the package manager’s Python bindings, a cloud-init hook, a backup
agent. The error message will not mention Ansible.
PEP 668 exists precisely because this happened often enough to be worth
a standard. On a distribution that implements it, the system Python is
marked externally managed and pip refuses. --break-system-packages is
not a workaround; it is a switch whose name tells you the outcome.
There is no per-project version. One machine, one Ansible. If two repositories on that controller need different versions — and during any upgrade, they do — you cannot have both.
Upgrades happen to you. pip install --upgrade of anything else on
that machine can move a shared dependency underneath Ansible. So can a
distribution update that replaces the system Python minor version,
which orphans everything pip installed into the old
site-packages path. The controller stops working, and nothing in your
change log mentions Ansible.
The honest summary: a system-wide pip install ansible produces a
controller whose version and dependency set are decided by the
interaction of two package managers that do not know about each other.
It is not that it never works. It is that when it stops working, the
cause is somewhere you would not think to look.
pipx — the workstation answer
pipx installs a Python application into its own private virtualenv and
puts the entry-point commands on your PATH. You get ansible,
ansible-playbook and the rest as commands, with none of their
dependencies visible to anything else.
sudo apt install pipx
pipx ensurepath
pipx install 'ansible-core==2.21.3'Collections are a separate matter — pipx installs the application, not
the collections, so ansible-galaxy collection install still runs
afterwards. If you need a Python library inside the Ansible
environment (a cloud SDK a collection depends on, for instance), pipx inject is the mechanism:
pipx inject ansible-core boto3Two versions can coexist with --suffix, which is what makes pipx
usable for an upgrade rehearsal:
pipx install --suffix=-next 'ansible-core==2.22.0'
ansible-playbook-next --syntax-check site.ymlpipx is the right default for a laptop or a personal jump box. It is
weaker for a shared controller because the environment is defined by the
commands you ran rather than by a file in the repository, and
pipx list is a description of a machine’s history, not a
specification.
The explicit virtualenv — the controller answer
A virtualenv is a directory containing an interpreter and a
site-packages. You name the Python, you name the versions, and both
facts live in your repository.
python3.12 -m venv /opt/estate/venv
/opt/estate/venv/bin/pip install --upgrade pip
/opt/estate/venv/bin/pip install 'ansible-core==2.21.3'
/opt/estate/venv/bin/ansible --versionConfirm what you built rather than assuming it:
$ ansible --versionansible [core 2.21.3]
config file = None
configured module search path = ['/home/ops/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /opt/estate/venv/lib/python3.14/site-packages/ansible
ansible collection location = /home/ops/.ansible/collections:/usr/share/ansible/collections
executable location = /opt/estate/venv/bin/ansible
python version = 3.14.4 (main, Jun 18 2026, 14:25:02) [GCC 15.2.0] (/opt/estate/venv/bin/python3)
jinja version = 3.1.6
pyyaml version = 6.0.3 (with libyaml v0.2.5)executable location under /opt/estate/venv is the proof. If it says
/usr/bin/ansible, you are running the distribution package regardless
of what you installed five minutes ago — the shell resolved ansible
from PATH and PATH did not have your venv on it.
The environment is then reproducible from one file:
# requirements.txt
ansible-core==2.21.3
A second machine runs the same three commands, gets the same
ansible --version, and the estate has two controllers that agree. That
test is the subject of the next lesson.
Choosing
For a workstation: pipx. You want commands on your PATH, you want
them isolated from the system Python, and you want pipx install --suffix available when it is time to try a new version.
For a shared or CI controller: an explicit virtualenv at a known
path, built from a requirements.txt in the repository, invoked by
absolute path. Nothing about which version is running should depend on
who is logged in.
For neither: a system-wide pip install. There is no situation where it is the best available option, because pipx does the same job without the shared dependency tree.
The blast-radius framing matters here as much as anywhere else in the course. The controller is the machine allowed to change every host you own. An install method that leaves its version ambiguous leaves the behaviour of every scheduled change ambiguous too.
Knowledge check
Knowledge check · 4 questions
Q1. A scheduled cron job on the controller behaves differently from the same command run interactively. Both invoke `ansible-playbook site.yml`. What should you check first?
Q2. What specifically goes wrong with `sudo pip install ansible` into the system Python? Select all that apply.
Q3. Why does this course prefer calling `/opt/estate/venv/bin/ansible-playbook` by absolute path rather than sourcing `activate` first?
Q4. Upgrading the base Python interpreter from 3.12 to 3.13 can break a virtualenv built from it.
Passing score: 75%. Answers are checked in this browser.