Skip to main content
RunBook Academy

AnsibleXXVII · Collections, Galaxy and Dependency TrustCollections and dependency trust

Private Galaxy, mirrors and air-gapped install

Advanced⏱ ~21 minansible-galaxyansible-config

What you'll learn

  • Build a self-contained collection bundle and install from it with no network
  • Configure GALAXY_SERVER_LIST with per-server authentication and understand the resolution order
  • State why a production controller fetching from public Galaxy at run time is an availability and integrity problem
  • Choose between a mirror, a bundle and an execution environment for a given estate

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

Not yet marked complete on this device.

A production controller that runs ansible-galaxy collection install against public Galaxy as part of a deployment has put a third-party website in the critical path of every change it makes.

That is three problems in one sentence:

  • Availability. Galaxy is down, or slow, or rate-limiting your CI runners, and your change window closes with nothing deployed.
  • Integrity. Whatever the endpoint serves today is what runs as root on your fleet today. Lesson 6’s verification only helps if you have something to compare against, and a fresh fetch is not it.
  • Reproducibility. The install is not the same install twice, for all the reasons lesson 4 laid out.

The fix is not exotic. Fetch deliberately, once, into an artefact you keep — then install from the artefact.

The offline bundle

ansible-galaxy collection download resolves the dependency tree and writes tarballs instead of installing them:

Configuration changefetch the tree, install nothing
$ ansible-galaxy collection download -r requirements.yml -p ./bundle
Process download dependency map
Starting collection download process to '/home/opsuser/project/bundle'
Downloading collection 'community.docker:4.7.0' to '/home/opsuser/project/bundle'
Downloading https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/community-docker-4.7.0.tar.gz to /home/opsuser/.ansible/tmp/ansible-local-1971711zm7rmrd/tmpwnwtvy3l/community-docker-4.7.0-v3o6lid7
Collection 'community.docker:4.7.0' was downloaded successfully
Downloading collection 'community.library_inventory_filtering_v1:1.1.5' to '/home/opsuser/project/bundle'
Collection 'community.library_inventory_filtering_v1:1.1.5' was downloaded successfully
Writing requirements.yml file of downloaded collections to '/home/opsuser/project/bundle/requirements.yml'

The last line is the useful part. The bundle is self-describing:

Read-only / Safewhat a bundle contains
$ ls -la ./bundle && cat ./bundle/requirements.yml
total 588
-rw-rw-r--  1 opsuser opsuser 557525 Aug 11 22:36 community-docker-4.7.0.tar.gz
-rw-rw-r--  1 opsuser opsuser  36002 Aug 11 22:36 community-library_inventory_filtering_v1-1.1.5.tar.gz
-rw-rw-r--  1 opsuser opsuser    147 Aug 11 22:36 requirements.yml

collections:
- name: community-docker-4.7.0.tar.gz
version: 4.7.0
- name: community-library_inventory_filtering_v1-1.1.5.tar.gz
version: 1.1.5

Note what the generated file contains: filenames, not collection names. That is a relative path, and it produces the most common failure with this workflow.

Configuration changeinstall with no network at all
$ cd ./bundle && ansible-galaxy collection install -r requirements.yml -p ../collections --offline
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Installing 'community.docker:4.7.0' to '/home/opsuser/project/collections/ansible_collections/community/docker'
community.docker:4.7.0 was installed successfully
Installing 'community.library_inventory_filtering_v1:1.1.5' to '/home/opsuser/project/collections/ansible_collections/community/library_inventory_filtering_v1'
community.library_inventory_filtering_v1:1.1.5 was installed successfully

No Downloading lines. Nothing was fetched. This install will produce byte-identical content on a machine with no route to the internet, in five years, from the same bundle.

--offline on install means do not contact any distribution server. It does not cover collections referenced by a git URL or a remote tarball URL — those are still network operations, which is one more reason a bundle should contain only artefacts.

Private Galaxy servers

The bundle is the air-gapped answer. For a connected estate that simply wants control, the answer is a server you run: Automation Hub, a Galaxy-compatible artefact repository, or a pull-through cache in whatever artefact manager you already have for .deb and container images.

The configuration lives in ansible.cfg:

Read-only / Safeansible.cfg
[galaxy]
server_list = internal_hub, community_mirror

[galaxy_server.internal_hub]
url = https://hub.example.com/api/galaxy/content/published/
token = REPLACE_ME

[galaxy_server.community_mirror]
url = https://hub.example.com/api/galaxy/content/community/
token = REPLACE_ME

The mechanics, from the setting’s own documentation:

  • server_list names sections. Each entry foo corresponds to a [galaxy_server.foo] header holding that server’s details.
  • Order is resolution order. A collection is looked for in the first server, then the second, and so on.
  • Setting server_list makes the single-server server setting ignored entirely. There is no implicit fallback to public Galaxy once you have a list — if you want it, it must be in the list, and that is a decision worth making explicitly rather than inheriting.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A bundle built with ansible-galaxy collection download is copied to a disconnected host, and the install fails with "name is not an FQCN. Could not find community-docker-4.7.0.tar.gz". What is the cause?

  2. Q2. Which are genuine problems with a pipeline that runs ansible-galaxy collection install from public Galaxy before every playbook run? Select all that apply.

  3. Q3. An ansible.cfg sets server_list = internal_hub, community_mirror. What is the effect on the public Galaxy server?

  4. Q4. A requirements file entry naming a git URL is still a network operation even when the install is run with --offline.

Passing score: 75%. Answers are checked in this browser.