AnsibleXXVII · Collections, Galaxy and Dependency TrustCollections and dependency trust
Shipping your own internal collection
What you'll learn
- Decide whether a body of automation should become a versioned collection
- Fill in galaxy.yml with the fields that matter to a consumer
- Build and install an internal collection, and read its version honestly
- Apply semantic versioning to automation, including what counts as a breaking change
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
Everything in this part so far has been about consuming other people’s automation carefully. This lesson turns it around: at some point your own roles become other people’s dependency, and every trust question you have been asking gets asked about you.
When it is worth it
A collection is a versioned, packaged, distributable boundary. Boundaries cost something. Do not build one until you have the problem it solves.
Build a collection when:
- Two or more repositories need the same automation, and the
current answer is a git submodule, a
roles/directory copied between them, or arequirements.ymlpointing at a git branch. Those all resolve to “whatever is on that branch today”, which is lesson 4’s problem in a different costume. - Consumers are not the authors. A platform team publishing roles for application teams needs a version number to have a conversation about, because “we changed it, re-run it” does not scale past the people you can walk over to.
- You need to ship plugins, not just roles. A
library/directory and afilter_plugins/directory at the top of a playbook repository work for one repository. A filter that three repositories need has to be in a collection; that is the only distribution mechanism for plugins. - You are vendoring a third-party fix. Lesson 5’s option 2: you need a patched module from a collection you cannot get a release out of. Putting it in your own collection gives the patch a version and a diff instead of a local edit that lesson 6’s verify will flag.
Do not build one when: you have one repository, one team, and roles
that are only ever used by the playbooks sitting next to them. A
roles/ directory is fine, and packaging it adds a build-and-publish
step to every change for no consumer’s benefit.
Scaffolding
$ ansible-galaxy collection init example_corp.platform --init-path ./collections_src- Collection example_corp.platform was created successfully$ find ./collections_src -maxdepth 4 | sort./collections_src
./collections_src/example_corp
./collections_src/example_corp/platform
./collections_src/example_corp/platform/docs
./collections_src/example_corp/platform/galaxy.yml
./collections_src/example_corp/platform/meta
./collections_src/example_corp/platform/meta/runtime.yml
./collections_src/example_corp/platform/plugins
./collections_src/example_corp/platform/plugins/README.md
./collections_src/example_corp/platform/README.md
./collections_src/example_corp/platform/rolesYour roles go under roles/, one directory each, in exactly the layout
they already have. Your plugins go under plugins/<type>/ — filter/,
lookup/, modules/, inventory/, and so on. Nothing about a role
changes when it moves into a collection except how it is addressed:
example_corp.platform.baseline rather than baseline.
galaxy.yml, and the fields a consumer reads
The generated file is heavily commented. The fields that matter to somebody running lesson 5’s review against you:
namespace: example_corp
name: platform
version: 1.0.0
readme: README.md
authors:
- Platform Engineering <platform@example.com>
description: Baseline host configuration and estate-wide primitives.
license:
- GPL-3.0-or-later
tags:
- infrastructure
# Collections this one requires, as namespace.name: version-range.
dependencies:
ansible.posix: '>=3.0.0,<4.0.0'
repository: https://git.example.com/platform/ansible-platform
documentation: https://docs.example.com/platform/ansible
homepage: https://docs.example.com/platform
issues: https://git.example.com/platform/ansible-platform/-/issues
build_ignore:
- .gitlab-ci.yml
- tests/integration/inventoryFour notes on filling it in.
namespace and name must be valid Python identifiers. Lowercase
alphanumeric and underscores, no hyphens, no leading digit, no
consecutive underscores — because they become package path components.
example_corp, not example-corp.
repository and issues are the two fields that make you
reviewable. A consumer following lesson 5’s checklist reads these
from MANIFEST.json after installing. If they point nowhere, you have
published something that fails your own intake review.
dependencies is a range, and you are now the collection whose
transitive dependency somebody else has to pin. Be conservative:
'>=3.0.0,<4.0.0' says what you tested and what you believe is
compatible. '*' says you did not think about it.
build_ignore keeps CI configuration and test fixtures out of the
artefact. galaxy.yml, *.pyc, *.retry and .git are always
filtered; everything else you ship, including anything you left in the
directory.
Build and install
$ ansible-galaxy collection build --output-path ../../../distCreated collection for example_corp.platform at /home/opsuser/project/dist/example_corp-platform-1.0.0.tar.gzThe filename encodes namespace, name and version, which is what makes a directory of these a usable local index.
$ ansible-galaxy collection install ./dist/example_corp-platform-1.0.0.tar.gz -p ./collections --offlineStarting galaxy collection install process
Process install dependency map
Starting collection install process
Installing 'example_corp.platform:1.0.0' to '/home/opsuser/project/collections/ansible_collections/example_corp/platform'
example_corp.platform:1.0.0 was installed successfully$ ansible-galaxy collection list -p ./collections# /home/opsuser/project/collections/ansible_collections
Collection Version
--------------------- -------
example_corp.platform 1.0.0Compare that with the same collection referenced as a source tree,
which is what a git clone into the collections directory gives you:
$ ansible-galaxy collection list -p ./collections[WARNING]: Collection at '/home/opsuser/project/collections/ansible_collections/example_corp/platform' does not have a MANIFEST.json file, nor has it galaxy.yml: cannot detect version.
# /home/opsuser/project/collections/ansible_collections
Collection Version
--------------------- -------
example_corp.platform *That * is the entire argument for doing this properly. Both
directories contain the same automation. Only one of them can be
pinned, verified, rolled back or reported in an audit.
Publishing to an internal server is one more command:
ansible-galaxy collection publish ./dist/example_corp-platform-1.0.0.tar.gz \
--server internal_hub--server names an entry from the server_list configured in lesson
7. The token comes from the environment, not from ansible.cfg.
Semantic versioning, applied to automation
Galaxy requires semantic versions and the resolver relies on them. The
mapping to automation is not always obvious, so state it explicitly in
your README.md:
| Change | Bump | Why |
|---|---|---|
| New role, or a new optional variable with a default | Minor | Existing consumers unaffected |
| Bug fix that makes a task correctly idempotent | Patch | Unless somebody depended on the wrong behaviour |
| New required variable with no default | Major | Every consumer’s play now fails until they set it |
| Renaming a variable, role or plugin | Major | Unless you ship a redirect and deprecate first |
| Changing a default value | Major | This is the one people get wrong |
| Dropping support for an OS the roles handled | Major | Consumers on it are broken |
Raising requires_ansible | Major | Consumers on older core cannot install |
Knowledge check
Knowledge check · 4 questions
Q1. An internal role changes the default of a variable from 2 to auto. No consumer edited a playbook. How should the collection be versioned?
Q2. Which situations justify packaging existing roles as a collection? Select all that apply.
Q3. ansible-galaxy collection build packages the current git commit, so an artefact built from a tagged checkout cannot contain uncommitted files.
Q4. A collection appears in ansible-galaxy collection list with a version of * on a production controller. What does that indicate about how it got there?
Passing score: 75%. Answers are checked in this browser.