Collections and FQCN
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.
ansible.builtin.copytasks: - 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.
# 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: - 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.
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.
- Writing bare
copy:orufw:and getting "module not found" because the module lives in a collection that is not installed —ansible.builtinis the only collectionansible-coreships. - Running
ansible-galaxy role install community.general— the role subcommand on a collection — and being confused when it fails, because collections install withansible-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.ymlbut 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 tocommunity.general, and the FQCN is what makes the move visible instead of a sudden break.
- 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:inrequirements.ymlalongside roles, and install from it in CI so every environment has the identical content. - Install with
ansible-galaxy collection install -r requirements.ymland 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 installfor collections,role installfor roles — so an install does not fail on a mismatched type.
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 incommunity.general, lives in a collection you must install ansible-coreships every official collection in the box, so only third-party community collections ever need installingcommunity.general— the core install bundles the most common community modules alongside the builtins- None at all — every collection including
ansible.builtinmust 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.generalis a collection, and collections install withansible-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 acollections:block, andansible-galaxy install -r requirements.ymlinstalls 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.ymlwhile roles live in a separateroles.ymlfile 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