AnsibleXXVII · Collections, Galaxy and Dependency TrustCollections and dependency trust
Proving which artefact you installed
What you'll learn
- Establish a complete controller manifest with four read-only commands
- Explain why ansible --version answers only a fraction of the question
- Diagnose "it works on my controller" from a manifest diff rather than by guessing
- State what ansible-core alone can and cannot do, from evidence rather than reputation
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
Part III argued which artefact belongs on a production controller —
ansible-core with pinned collections rather than the batched community
package. That argument is settled. This lesson is about the operational
sequel: on the machine in front of you right now, which one is
installed, and how would you prove it to somebody who disagrees?
The question comes up in exactly one situation, and it comes up
constantly: a playbook works for one engineer and fails for another,
from the same commit, against the same inventory. The instinct is to
compare ansible --version. That comparison is close to useless on its
own, and this lesson is mostly about why.
What ansible --version does and does not tell you
$ ansible --versionansible [core 2.21.3]
config file = None
configured module search path = ['/home/opsuser/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /opt/ansible/venv/lib/python3.12/site-packages/ansible
ansible collection location = /home/opsuser/.ansible/collections:/usr/share/ansible/collections
executable location = /opt/ansible/venv/bin/ansible
python version = 3.12.7 [GCC 13.2.0] (/opt/ansible/venv/bin/python3)
jinja version = 3.1.6
pyyaml version = 6.0.3 (with libyaml v0.2.5)Illustrative output
It answers four questions honestly:
- Which
ansible-core.core 2.21.3— the engine version. - Which Python and which install. The
executable locationandpython versionlines together identify the virtualenv or pipx environment. If those two disagree with what you expected, stop here; you are on a different controller than you thought. - Which config file.
config file = Nonemeans noansible.cfgwas found — which is itself a finding, because a project that expects one has just been run from the wrong directory. - Where collections would be looked for.
And here is what it does not tell you: which collections are
actually there. That is the whole gap. core 2.21.3 on two machines
is entirely compatible with one of them having community.docker and
the other not, or with the two having versions two years apart.
The four commands that make a manifest
A controller manifest is four read-only commands. Run them on both machines, diff the output, and the difference is in front of you.
ansible --version
ansible-galaxy collection list
ansible-config dump --only-changed
pip list 2>/dev/null | grep -Ei 'ansible|jinja|resolvelib'Take them in turn.
ansible-galaxy collection list
This is the command the version banner is missing. On a bare
ansible-core install it prints nothing at all:
$ ansible-galaxy collection listNo output. No error, no headers, exit status 0. That is what
ansible-core on its own looks like, and it is the single most useful
piece of evidence in this diagnosis: it means every module in the
repository must be ansible.builtin or the run will fail.
Once something is installed, the output names the path it was found under, which matters as much as the versions:
$ ansible-galaxy collection list -p ./gx# /home/opsuser/project/gx/ansible_collections
Collection Version
---------------------------------------- -------
community.docker 4.7.0
community.library_inventory_filtering_v1 1.1.5Two collections from one request. The second is a transitive dependency, and it is the subject of lesson 4.
There is a machine-readable form, which is what you want in CI:
$ ansible-galaxy collection list -p ./gx --format json{"/home/opsuser/project/gx/ansible_collections": {"community.docker": {"version": "4.7.0"}, "community.library_inventory_filtering_v1": {"version": "1.1.5"}}}--format yaml is also accepted. Either beats parsing the human table,
which is aligned with spaces and will change.
ansible-config dump --only-changed
Part VI covered this command in full. Its role in the manifest is
narrow and important: it reduces a couple of hundred settings to the
handful this controller has altered, including the ones set by
environment variables that no file records. A controller with
ANSIBLE_COLLECTIONS_PATH exported in one engineer’s shell profile is
a different controller, and this is the command that shows it.
pip list
The engine’s own dependencies. Most of the time this is noise; twice a
year it is the answer, when a Jinja or resolvelib version explains a
templating or dependency-resolution difference the other three commands
cannot.
$ pip list | grep -Ei 'ansible|jinja|resolvelib|pyyaml'ansible-compat 26.6.0
ansible-core 2.21.3
ansible-lint 26.6.0
Jinja2 3.1.6
PyYAML 6.0.3
resolvelib 1.2.1Note what is absent: there is no package called ansible in that
list. That absence is the proof that this controller has ansible-core
rather than the community package — the community package installs a
distribution literally named ansible, and it carries hundreds of
collections with it.
What ansible-core alone actually gives you
Not an opinion — a count:
$ ansible-doc -t module -l ansible.builtin | wc -l71Seventy-one modules. That covers files, templates, packages via the
package/apt/dnf family, services, users, groups, cron, systemd,
mounts via ansible.posix… no. mount is not in that seventy-one;
it is in ansible.posix, which is a collection.
That last correction is the shape of every surprise in this area. The
modules people assume are “core” and are not include ansible.posix.mount,
ansible.posix.sysctl, ansible.posix.firewalld, community.general.timezone
and community.general.ufw. They feel core because they are ubiquitous
in examples written against the community package.
Knowledge check
Knowledge check · 4 questions
Q1. ansible-galaxy collection list prints nothing at all and exits 0. What have you learned?
Q2. A playbook works for engineer A and fails for engineer B with "Cannot resolve" on a module name. Both report core 2.21.3. Which findings would explain it? Select all that apply.
Q3. Which command output distinguishes an ansible-core install from the community ansible package most directly?
Q4. ansible.builtin.mount is available on any controller running ansible-core 2.21.
Passing score: 75%. Answers are checked in this browser.