Free tech learning for everyone, from basics to pro

AnsibleProgramming and Application SecurityIntermediate

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
Standard role layouttasks/main.ymlhandlers/main.ymltemplates/.j2 filesdefaults/overridable varsvars/fixed varsmeta/dependenciesfiles/static filestests/example playsREADME.mdhow to use
Figure 1. ansible-galaxy init creates the same folder layout for every role, so anyone knows where to look.

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

Organising automationCollectionnamespace.name, sharedRolesreusable unitsPlaybookswhich roles whereInventorywhich devices
Figure 2. Collections package roles and modules; playbooks apply roles to inventory groups.

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.

ItemHoldsExample
Task fileA few related tasksntp.yml
RoleEverything for one functionbase_switch
CollectionRoles, modules and plugins for sharingmyorg.network
ProjectInventory, playbooks, requirementsA 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?
In defaults/main.yml, which has the lowest precedence.
What is the difference between import_role and include_role?
import_role is processed statically when the playbook is parsed; include_role is dynamic and evaluated during the run, so it works in loops and conditions.
Why pin collection versions in requirements.yml?
So every engineer and pipeline uses the same tested code and a new release cannot change behaviour unexpectedly.

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.
Tracks your learning on this device only. See your progress on the learning paths.

About the author

Mohamed Fayas

Founder of FM TecHub. CCNA certified, with a BTEC HND in Network Engineering, and BEng (Hons) in Computer Networking and Cloud Security. He writes practical, plain-English guides on networking, security and cloud.

More about the author

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.

Stay up to date by email

New notes, tools and security news from FM TecHub, explained simply. Free.

We only use your email to send these updates. See our privacy policy.