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

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, pick ansible-role-refactor, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.