Consuming and Publishing Shared Content
Scenario: A production playbook depends on a community role installed by hand on one engineer's machine two years ago. Nobody knows the version, and the machine is being replaced.
New words, in plain English
requirements.yml- the declared, committed list of external roles and collections a project needs.- Pinning - specifying an exact version so installs are reproducible.
ansible-galaxy init- generates a skeleton role or collection with the standard layout.- Air-gapped - an environment with no access to the public internet.
External content should be treated exactly like any other dependency: declared, pinned, committed, and installed by automation rather than by hand.
requirements.yml is the declaration. ansible-galaxy install -r requirements.yml is the install. Both belong in CI, so the runner builds the same dependency set every time and a fresh laptop is one command away from working. The scenario above is what happens without them - an undocumented dependency living only on hardware.
When you publish rather than consume, ansible-galaxy init generates the standard skeleton, which is worth using even for internal roles: it produces the conventional directories, a meta/main.yml and a README.md stub, so your role looks like every other role an engineer has ever opened.
For air-gapped or regulated environments, collections can be downloaded as tarballs and installed from a local path or a private Automation Hub. Doing this deliberately also removes a public registry from your deployment critical path, which is worth something even where the internet is available.
Analogy: requirements.yml is the ingredients list on a recipe, with quantities. Without it you have a dish that only one person can make, and only while they still remember what they used.
A worked example
# requirements.yml - committed to the repository
collections:
- name: community.general
version: "9.2.0"
- name: ansible.posix
version: "1.5.4"
# From a private Git repository
- name: https://git.internal/platform/ansible-collection.git
type: git
version: v2.3.0
roles:
- name: geerlingguy.postgresql
version: "3.5.2"
# In CI, before anything else runs:
ansible-galaxy install -r requirements.yml
# Starting a new internal role with the standard layout
ansible-galaxy init roles/checkout-api
A useful CI habit is to install dependencies into a project-local path (ansible-galaxy install -r requirements.yml -p ./.galaxy) and set collections_path and roles_path accordingly. That guarantees the runner uses exactly the pinned set rather than anything that happens to be installed globally on the image - which is the same class of problem that virtual environments solve for Python.
Upgrading a pinned dependency should be a deliberate, reviewable change: bump the version, run the playbook against a staging environment, check --diff output for behaviour changes, then merge. Treating it as a routine dependency bump rather than an invisible drift is the entire point of pinning.
Tip: Add ansible-galaxy install -r requirements.yml as the first step of your CI job. It turns "works on my machine" into a reproducible, reviewable fact.
Goal: Put this to work in the ansible-role-refactor lab. Open/labs/ansible, pickansible-role-refactor, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.