Introduction

Ansible roles and collections both package reusable automation, but they solve different problems. Roles bundle tasks, handlers, templates, and variables for a single purpose. Collections bundle roles, modules, plugins, and playbooks into a distributable namespace. Understanding the difference prevents you from building roles when you need collections, or over-engineering collections when a role suffices.

Quick Comparison

FeatureRolesCollections
ContainsTasks, handlers, templates, vars, filesRoles + modules + plugins + playbooks
NamespaceName only (nginx)Namespace.name (community.general)
DistributionAnsible Galaxy (or git)Ansible Galaxy, Automation Hub, git
VersioningGit tagsSemantic versioning
Custom modules❌ Not standard✅ First-class support
Custom plugins❌ Not standard✅ Filters, lookups, callbacks, etc.
Dependenciesmeta/main.ymlgalaxy.yml with collections list
Install commandansible-galaxy role installansible-galaxy collection install

Roles — Single-Purpose Automation

roles/nginx/
├── defaults/main.yml       # Default variables (user overrides)
├── vars/main.yml           # Internal variables
├── tasks/main.yml          # Main task list
├── handlers/main.yml       # Handlers (service restarts)
├── templates/              # Jinja2 templates
│   └── nginx.conf.j2
├── files/                  # Static files
│   └── ssl-params.conf
├── meta/main.yml           # Role metadata + dependencies
└── README.md
# roles/nginx/tasks/main.yml
---
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present

- name: Deploy configuration
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: Restart nginx

- name: Ensure running
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true
# Using the role
- hosts: webservers
  roles:
    - role: nginx
      vars:
        nginx_worker_connections: 2048

Install Roles

# From Galaxy
ansible-galaxy role install geerlingguy.nginx

# From git
ansible-galaxy role install git+https://github.com/user/ansible-role-nginx.git

# From requirements.yml
ansible-galaxy role install -r requirements.yml
# requirements.yml
roles:
  - name: geerlingguy.nginx
    version: "3.2.0"
  - name: geerlingguy.postgresql
    version: "3.4.0"

Collections — Namespaced Packages

namespace/collection_name/
├── galaxy.yml              # Collection metadata
├── plugins/
│   ├── modules/            # Custom modules
│   │   └── my_module.py
│   ├── filter/             # Custom filters
│   │   └── my_filter.py
│   ├── lookup/             # Lookup plugins
│   └── callback/           # Callback plugins
├── roles/
│   ├── install/            # Roles included in collection
│   └── configure/
├── playbooks/              # Pre-built playbooks
│   └── deploy.yml
└── docs/
# galaxy.yml
namespace: mycompany
name: infrastructure
version: "1.0.0"
description: Internal infrastructure automation
dependencies:
  ansible.builtin: ">=2.15"
  community.general: ">=7.0.0"

Install Collections

# From Galaxy
ansible-galaxy collection install community.general

# Specific version
ansible-galaxy collection install community.general:==9.0.0

# From Automation Hub (AAP)
ansible-galaxy collection install --server https://hub.example.com mycompany.infra

# From requirements.yml
ansible-galaxy collection install -r requirements.yml
# requirements.yml
collections:
  - name: community.general
    version: ">=9.0.0"
  - name: amazon.aws
    version: "7.0.0"
  - name: community.docker

Using Collections

# Fully qualified collection name (FQCN) — recommended
- name: Manage Docker container
  community.docker.docker_container:
    name: myapp
    image: myapp:latest

# Or declare at play level
- hosts: all
  collections:
    - community.general
    - community.docker
  tasks:
    - docker_container:  # Short name (less explicit)
        name: myapp

When to Use Each

Use a Role When...

# Single-purpose task bundle: "install and configure X"
roles:
  - nginx           # Install + configure nginx
  - postgresql      # Install + configure PostgreSQL
  - deploy_app      # Deploy your application

# Internal automation that doesn't need custom modules
# Simple enough that tasks + templates + handlers cover it

Use a Collection When...

# You're building or using custom modules
- mycompany.infra.provision_server:
    name: web3
    size: large

# You need plugins (filters, lookups, callbacks)
- debug:
    msg: "{{ data | mycompany.utils.custom_filter }}"

# You're distributing automation to multiple teams
# You need versioned, namespaced packages

Real-World Decision

Need custom modules or plugins?
├─ Yes → Collection
└─ No  ↓

Distributing to other teams/orgs?
├─ Yes (with versioning) → Collection
├─ Yes (simple sharing)  → Role on Galaxy
└─ No (internal only)    → Role in repo

Just tasks + templates + handlers?
├─ Yes → Role
└─ No  → Collection

Collections in Ansible 13

Ansible 13 (ansible-core 2.20) made changes to included collections:

# Removed in Ansible 13
# community.digitalocean → Use community.general
# ibm.qradar → Use ibm.security  
# awx.awx → Deprecated, use ansible.controller

# Check installed collections
ansible-galaxy collection list

# Update all collections
ansible-galaxy collection install --upgrade -r requirements.yml

Common Mistakes

# ❌ Putting custom modules in a role's library/ dir
roles/myrole/library/my_module.py
# This works but is not the standard — hard to share, no namespace

# ✅ Create a collection for custom modules
mycompany/utils/plugins/modules/my_module.py

# ❌ Using short module names (ambiguous)
- docker_container:
    name: myapp

# ✅ Use FQCN (Fully Qualified Collection Name)
- community.docker.docker_container:
    name: myapp

# ❌ Not pinning collection versions
# requirements.yml
collections:
  - community.general  # Could break on major update!

# ✅ Pin versions
collections:
  - name: community.general
    version: ">=9.0.0,<10.0.0"

Conclusion

Roles = tasks + templates + handlers for a single purpose ("install nginx", "deploy my app"). Collections = namespaced packages containing roles + modules + plugins ("community.docker", "amazon.aws"). Most users consume collections and write roles. Build a collection when you need custom modules/plugins or when distributing automation across teams. Always use FQCN in playbooks and pin collection versions in requirements.yml.