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
| Feature | Roles | Collections |
|---|---|---|
| Contains | Tasks, handlers, templates, vars, files | Roles + modules + plugins + playbooks |
| Namespace | Name only (nginx) | Namespace.name (community.general) |
| Distribution | Ansible Galaxy (or git) | Ansible Galaxy, Automation Hub, git |
| Versioning | Git tags | Semantic versioning |
| Custom modules | ❌ Not standard | ✅ First-class support |
| Custom plugins | ❌ Not standard | ✅ Filters, lookups, callbacks, etc. |
| Dependencies | meta/main.yml | galaxy.yml with collections list |
| Install command | ansible-galaxy role install | ansible-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"
Related Articles
- Ansible Roles Guide
- Ansible Galaxy Guide
- Ansible group_vars vs host_vars vs role defaults
- Upgrade to Ansible 13
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.