Ansible roles and collections: organising automation
In short: Roles package related tasks, handlers, templates and variables into a reusable unit such as "ntp" or "base_switch". Collections bundle roles, modules and plugins for sharing. Together they keep large automation projects tidy and reusable.
Why roles
A single playbook is fine for a lab. Once you have NTP, syslog, SNMP, AAA, VLANs and banners for several platforms, one file becomes hundreds of lines and hard to reuse. A role groups everything for one job into a standard folder structure. A playbook then becomes a short list of which roles apply to which groups of devices.
ansible-galaxy role init roles/base_switch
defaults versus vars
Both folders hold variables, but with very different precedence. defaults/main.yml has the lowest precedence: it provides sensible values that inventories can easily override. vars/main.yml has high precedence and is for values that should not normally change, such as internal file paths. Put anything a user might tune, such as NTP servers or a banner text, in defaults.
# roles/base_switch/defaults/main.yml
base_switch_ntp_servers: []
base_switch_banner: "Authorised access only"
# roles/base_switch/tasks/main.yml
- name: Apply login banner
cisco.ios.ios_banner:
banner: motd
text: "{{ base_switch_banner }}"
state: present
notify: Save config
- name: Apply NTP servers
ansible.builtin.include_tasks: ntp.yml
when: base_switch_ntp_servers | length > 0
Prefixing variable names with the role name (base_switch_) avoids clashes when several roles are used together.
Using roles in playbooks
# site.yml
- name: Baseline for access switches
hosts: access
gather_facts: false
roles:
- base_switch
- role: snmp_v3
vars:
snmp_v3_view: READONLY
You can also call roles from tasks with ansible.builtin.import_role (processed when the playbook loads) or ansible.builtin.include_role (processed during the run, so it can be inside loops and conditions).
Collections and sharing
A collection is the distribution format for Ansible content. It has a namespace and a name, such as cisco.ios or myorg.network, and can contain modules, plugins, roles and playbooks. You install public collections from Ansible Galaxy, the community hub, or supported ones from Red Hat Automation Hub. Many organisations also run a private hub for internal collections.
# requirements.yml
collections:
- name: cisco.ios
version: ">=9.0.0,<10.0.0"
- name: ansible.utils
roles:
- name: myorg.base_linux
src: git+https://git.example.com/netops/base_linux.git
version: v1.4.0
# install everything
ansible-galaxy install -r requirements.yml
Pinning version ranges stops a new major release changing behaviour in the middle of a change window. Review third-party roles before use: they run with the same privileges as your own code.
| Item | Holds | Example |
|---|---|---|
| Task file | A few related tasks | ntp.yml |
| Role | Everything for one function | base_switch |
| Collection | Roles, modules and plugins for sharing | myorg.network |
| Project | Inventory, playbooks, requirements | A Git repository |
Tip: give every role a README listing its variables and an example playbook. A role nobody understands will not be reused.
Practice
Where should a role keep a variable that users are expected to override?
defaults/main.yml, which has the lowest precedence.What is the difference between import_role and include_role?
Why pin collection versions in requirements.yml?
Key takeaways
- Roles bundle tasks, handlers, templates and variables for one job.
- Put tunable values in defaults and fixed values in vars.
- Prefix role variables with the role name to avoid clashes.
- Collections package roles, modules and plugins with a namespace.
- Pin versions in requirements.yml and review third-party content.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.