Chapter 8: Roles & Reuse
Topic 50

Collections and FQCN

CollectionsFQCN

A collection is the modern packaging unit for Ansible content — modules, roles, plugins, and playbooks bundled under a namespace.collection name like community.general or ansible.posix. The fully-qualified collection name, or FQCN, namespace.collection.module is now the way you address every module: ansible.builtin.copy, not bare copy. This is the Ansible-identity packaging shift — ansible-core ships only the ansible.builtin collection, and everything else, from the cloud modules to community.general.*, installs as a collection you declare.

If you learned Ansible years ago writing bare copy: and service:, this is the change that reshapes the daily syntax. The modules did not vanish; they live in collections now, and the FQCN is how you name exactly which one you mean.

What a Collection Bundles

A collection packages modules, plugins, roles, and playbooks under one versioned namespace.collection name. ansible-core is deliberately small — it ships only ansible.builtin, the handful of modules every install needs. Everything beyond that is a separate collection you add: amazon.aws for AWS, community.general for the long tail of community modules, community.postgresql for Postgres management. The split keeps the core install lean and lets each collection version and release on its own schedule, independent of the engine.

FQCN — namespace.collection.module

The FQCN is three dotted parts: namespace, collection, module. ansible.builtin.copy, community.general.ufw, community.postgresql.postgresql_db. Writing the FQCN removes the ambiguity of a bare copy — there is exactly one ansible.builtin.copy, and the name says where it comes from. It is the form linters expect, the form current documentation uses, and the form that survives a module moving between collections, because the move changes the FQCN visibly instead of breaking a bare name silently.

The FQCN path — namespace to collection to module
ansible
the namespace — who publishes it
builtin
the collection — the package within
copy
the module — ansible.builtin.copy
FQCN in a playbook — every module fully addressed
tasks:
  - name: Render the site config
    ansible.builtin.template:      # ships in ansible-core
      src: larkspur.conf.j2
      dest: /etc/nginx/sites-available/larkspur.conf

  - name: Open the firewall for HTTPS
    community.general.ufw:          # needs community.general installed
      rule: allow
      port: "443"
      proto: tcp

The first task uses ansible.builtin.template, which is always available. The second uses community.general.ufw, which fails with "module not found" unless that collection is installed — and the FQCN is what makes that requirement visible at the call site instead of a runtime surprise.

ansible-galaxy collection install

Collections install with a different subcommand than roles: ansible-galaxy collection install community.general. The command pulls the collection into a collections path, separate from the roles path. This is a frequent newcomer trip — ansible-galaxy role install and ansible-galaxy collection install are distinct subcommands, and running the role one on a collection name fails confusingly. Roles and collections are different units installed by different commands, even though both come from Galaxy.

Installing a collection — note the subcommand
# a COLLECTION — use 'collection install', not 'role install'
$ ansible-galaxy collection install community.general

Run ansible-galaxy role install community.general by mistake and it fails or behaves nothing like you expect, because community.general is a collection, not a role. The subcommand has to match the unit.

The collections: Keyword and Why FQCN Won Anyway

A play-level collections: list lets you write short module names by declaring a search path — list community.general and you can write ufw instead of the full FQCN. The current guidance is to skip it and write the FQCN directly anyway. The search path reintroduces exactly the bare-name ambiguity the FQCN exists to remove: two collections with a same-named module, and the search order decides which one runs, silently. Explicit FQCN is unambiguous, and it survives a module relocating between collections without a search-path edit. That is why FQCN won even though the shortcut exists.

requirements.yml for Both Roles and Collections

One requirements.yml declares the whole external surface — a roles: block and a collections: block in the same file. ansible-galaxy install -r requirements.yml installs both, or you run the per-type ansible-galaxy collection install -r requirements.yml when you want just the collections. Pin versions on both, and the file is the single reproducible record of everything your project pulls from Galaxy — collections and roles together, the same way a lockfile records every dependency.

requirements.yml — collections and roles in one pinned manifest
# requirements.yml
collections:
  - name: community.general
    version: "8.6.0"
  - name: community.postgresql
    version: "3.4.0"
roles:
  - name: geerlingguy.nginx
    version: "3.1.4"

Install with ansible-galaxy install -r requirements.yml and both blocks resolve from one file, every collection and role pinned. A collection can ship roles inside it — larkspur.web could contain the larkspur_web role — so the same manifest scales from a borrowed module to a whole packaged stack.

Roles vs Collections

Role — a single reusable unit of tasks, handlers, templates, and defaults, applied with roles:, import_role, or include_role and installed with ansible-galaxy role install. Reach for it when you are packaging one concern, like configuring the web tier.

Collection — a larger package that can contain many roles plus modules and plugins, addressed by FQCN and installed with ansible-galaxy collection install. A collection often ships roles inside it — larkspur.web could contain the larkspur_web role — so the collection is the outer package and the role a unit within or alongside it.

Common Mistakes
  • Writing bare copy: or ufw: and getting "module not found" because the module lives in a collection that is not installed — ansible.builtin is the only collection ansible-core ships.
  • Running ansible-galaxy role install community.general — the role subcommand on a collection — and being confused when it fails, because collections install with ansible-galaxy collection install.
  • Declaring collections only on the control node and forgetting requirements.yml, so CI runs without the collection and the play fails where it passed locally.
  • Pinning a collection in requirements.yml but using a bare module name that resolves to a different collection's same-named module, applying the wrong module silently.
  • Assuming a module that worked years ago at its bare name still ships in ansible-core — many moved out to community.general, and the FQCN is what makes the move visible instead of a sudden break.
Best Practices
  • Write the full FQCN — ansible.builtin.copy, community.postgresql.postgresql_db — for every module, so the source collection is explicit and a module relocating between collections does not break the play.
  • Declare every collection with a pinned version: in requirements.yml alongside roles, and install from it in CI so every environment has the identical content.
  • Install with ansible-galaxy collection install -r requirements.yml and keep that file the single source of external dependencies, roles and collections together.
  • Prefer explicit FQCN over the collections: search-path keyword, since the search path reintroduces the bare-name ambiguity FQCN exists to remove.
  • Match the subcommand to the unit — collection install for collections, role install for roles — so an install does not fail on a mismatched type.
Comparable tools Terraform provider — namespaced hashicorp/aws, pinned by version, the closest analog to a packaged collection npm scoped packages @scope/pkg mirror the namespace model Puppet · Chef — no construct maps cleanly; this is Ansible-specific packaging

Knowledge Check

Which collection does ansible-core ship, and what does that mean for everything else?

  • Only ansible.builtin — every other module, like those in community.general, lives in a collection you must install
  • ansible-core ships every official collection in the box, so only third-party community collections ever need installing
  • community.general — the core install bundles the most common community modules alongside the builtins
  • None at all — every collection including ansible.builtin must be installed by hand before its first use

What does an FQCN like community.postgresql.postgresql_db address, and why does it matter now?

  • It names namespace, collection, and module exactly, so a module moving between collections breaks loudly instead of silently resolving a bare name
  • It is purely cosmetic — the bare module name always resolves identically, so the FQCN form merely changes how the auto-generated documentation renders
  • It sets the module's precedence relative to the roles and tasks running in the same play
  • It tells Ansible to run the module locally on the control node rather than on the managed host

You run ansible-galaxy role install community.general and it fails. Why?

  • community.general is a collection, and collections install with ansible-galaxy collection install — a different subcommand than roles
  • The role of that name exists on Galaxy but requires an explicit version pin that the install command omitted, so the resolver gave up
  • Galaxy rate-limited the request from your IP; the command itself is perfectly correct
  • Role names cannot contain a dot, so the dotted namespace broke the argument parse

How does requirements.yml handle both roles and collections?

  • One file holds a roles: block and a collections: block, and ansible-galaxy install -r requirements.yml installs both with their pinned versions
  • You need two entirely separate files — one dedicated to roles and one to collections — because the two kinds simply cannot coexist within a single manifest
  • Collections go in requirements.yml while roles live in a separate roles.yml file by established convention
  • Only collections can be declared in requirements.yml; roles must each be installed one at a time by hand

You got correct