Skip to content

Ansible roles / importrole / includerole Practical Reference

About This Article

A practical reference for engineers who struggle with choosing between Ansible's roles:, import_role, and include_role.

What You'll Learn:

  1. Use flowcharts to instantly decide which approach to use
  2. Find the right method from reverse lookup tables based on "what you want to do"
  3. Identify causes and solutions from troubleshooting tables when issues occur

How to Use This Document


Decision Flowchart

Just listing roles as play structure?
│
├─ Yes ───────────────────────────→ roles:
│
└─ No → Need to insert tasks in between?
         │
         ├─ No ───────────────────→ roles:
         │
         └─ Yes → Need conditionals/loops/variable role names?
                   │
                   ├─ No ─────────→ import_role
                   │                 (static - tags apply to role tasks)
                   │
                   └─ Yes ────────→ include_role
                                     (dynamic - supports conditionals/loops)
                                     │
                                     └─ Want tags to apply to role tasks?
                                         ├─ Yes → Use with apply:
                                         └─ No  → Use as-is

Reverse Lookup Table (Goal → Method)

GoalMethodNotes
Define play structureroles:First choice. Auto dependency resolution
Insert tasks in betweenimport_roleExplicit positioning
Apply tags to all role tasksimport_roleinclude_role requires apply:
Conditional role selectioninclude_roleUse with when:
Loop over rolesinclude_roleUse with loop:
Variable role nameinclude_rolename: "{{ var }}"
Execute specific role file onlytasks_from:Limit entry points
Expose role vars to subsequent tasksinclude_role + public: trueExplicit required
Execute same role multiple timesallow_duplicates: true (in role)Set in meta/main.yml

Troubleshooting Table (Symptom → Solution)

SymptomCauseSolution
Tags don't affect role tasksinclude_role tags don't inherit to roleUse apply: or switch to import_role
Second invocation of same role skippedDuplicate prevention (default)Add allow_duplicates: true in role
Variables affect preceding tasksimport_role exposes vars at parse timeExplicit public + isolate shared vars
Role doesn't work in handlersRole calls in handlers not supportedLimit handlers to single functions, use role calls in tasks
Wrong handler triggered due to name collisionHandlers have Play-wide scopeUse role_name : handler_name format in notify

Characteristics of the Three Approaches

Static vs Dynamic

TypeProcessing TimingCharacteristics
Static (roles:, import_role)Parse timeExecution plan determined early. Tags easily apply to role tasks
Dynamic (include_role)RuntimeStrong support for conditionals/loops. Tags require apply:

Overview of Each Approach

MethodUse CaseCharacteristics
roles:Play structureStatic. Auto dependency resolution. Runs before tasks
import_roleInsert tasks + apply tagsStatic. Can be positioned in tasks list
include_roleConditionals/loops/variable namesDynamic. Flexible but tags need apply:

Execution Order

Roles specified with roles:  ←── Execute first
        ↓
tasks: task list (import_role/include_role inserted here)
  • roles: runs before tasks
  • import_role/include_role execute at their position in tasks list

How Tags Work (Critical Difference)

MethodTag ScopeWorkaround
roles: / import_roleApplied to all role tasksWorks as-is
include_roleApplied only to include_role statementUse apply: to pass to role

include_role + apply example:

- name: deploy with tags
  ansible.builtin.include_role:
    name: app
    apply:
      tags: [deploy]
      become: true
  tags: [deploy]  # ← Tag for include_role statement itself

Call Specific Files (tasks_from, etc.)

- ansible.builtin.include_role:
    name: myrole
    tasks_from: setup.yml      # roles/myrole/tasks/setup.yml
    handlers_from: custom.yml  # roles/myrole/handlers/custom.yml

Team Standard: Limit entry points to a few (install/configure/verify). Split roles if they grow.


Variable Scope and public

MethodExposure Range with public
include_roleAvailable from that task onwards
import_roleExposed at parse time (may affect preceding tasks)
- ansible.builtin.include_role:
    name: shared_vars
    public: true  # ← Explicit required

Team Standard: Always explicitly set public (true/false). Isolate shared variables in dedicated roles.


Duplicate Prevention and allow_duplicates

Principle: Same role executes only once per Play (including dependent roles)

To execute multiple times:

# roles/myrole/meta/main.yml
allow_duplicates: true

Pattern Collection

A. Play Structure (Default)

- hosts: all
  roles:
    - common
    - hardening
    - app

B. Insert Check in Between

- hosts: web
  tasks:
    - name: precheck
      command: /usr/local/bin/precheck

    - ansible.builtin.import_role:
        name: app
      tags: [deploy]

C. Conditional Branching

- hosts: all
  tasks:
    - ansible.builtin.include_role:
        name: app_debian
      when: ansible_facts['os_family'] == 'Debian'

    - ansible.builtin.include_role:
        name: app_rhel
      when: ansible_facts['os_family'] == 'RedHat'

D. Loop

- hosts: all
  tasks:
    - ansible.builtin.include_role:
        name: "{{ item }}"
      loop:
        - common
        - app

E. include_role + apply

- hosts: web
  tasks:
    - ansible.builtin.include_role:
        name: app
        apply:
          tags: [deploy]
          become: true
      tags: [deploy]

Team Standardization (Minimal Rules)

  1. Default to roles:
  2. Document exception conditions
  3. Insert tasks → import_role
  4. Conditionals/loops/variables → include_role (with apply: to affect role tasks)
  5. Always explicitly set public (true/false)
  6. Limit tasks_from entry points (split roles if they grow)
  7. Use allow_duplicates in role for multiple executions
  8. Prohibit role calls in handlers (design guideline)

References


Updated: 2025-12-29