AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible import_role vs include_role: Static vs Dynamic Role Loading

By Luca Berton · Published 2024-01-01 · Category: installation

Complete comparison of Ansible import_role vs include_role. Understand static vs dynamic loading, variable scope, handler behavior, tag inheritance, loop.

Ansible loads roles two ways: import_role (static, at parse time) and include_role (dynamic, at runtime). The difference affects tags, handlers, loops, conditionals, and error handling. Choosing wrong causes subtle bugs.

Quick Comparison

Featureimport_role (static)include_role (dynamic)
When loadedParse time (before play starts)Runtime (when task executes)
TagsInherited by all tasks in roleApplied to include task only
HandlersAvailable globallyAvailable after include runs
Loop support❌ No✅ Yes
Conditional (when)Applied to every task in roleApplied once to include decision
Variable file loadingImmediateDeferred
--list-tasksShows role tasksShows "include_role: name=X"
--list-tagsShows role tagsHidden
Error on missingFails at parse timeFails at runtime

import_role: Static Loading

import_role loads the role at parse time — before any task runs. All tasks, handlers, and variables are merged into the play.

- name: Deploy web application
  hosts: webservers
  tasks:
    - name: Set up base system
      ansible.builtin.import_role:
        name: base_system

    - name: Configure nginx
      ansible.builtin.import_role:
        name: nginx
      vars:
        nginx_port: 8080

    - name: Deploy application
      ansible.builtin.import_role:
        name: deploy_app

Tags Work as Expected

# With import_role, tags apply to individual tasks INSIDE the role
- ansible.builtin.import_role:
    name: nginx

# If nginx role has tasks tagged "config" and "install":
# ansible-playbook site.yml --tags config
# → Runs ONLY the "config" tasks from the nginx role ✅

Conditionals Apply to Every Task

# when: is applied to EVERY task inside the role
- ansible.builtin.import_role:
    name: nginx
  when: ansible_os_family == "Debian"

# This means EVERY task in the nginx role gets:
#   when: ansible_os_family == "Debian"
# Each task is evaluated individually (and shows as skipped)

See also: Ansible-Lint: Complete Guide to Linting Playbooks & Roles

include_role: Dynamic Loading

include_role loads the role at runtime — only when the task executes. The role is treated as a single unit.

- name: Deploy services
  hosts: all
  tasks:
    - name: Include role based on OS
      ansible.builtin.include_role:
        name: "{{ ansible_os_family | lower }}_setup"

    - name: Include service roles dynamically
      ansible.builtin.include_role:
        name: "{{ item }}"
      loop:
        - nginx
        - redis
        - postgresql

Loop Support

# ✅ include_role supports loops — import_role does NOT
- name: Deploy multiple services
  ansible.builtin.include_role:
    name: "{{ service.role }}"
  vars:
    service_port: "{{ service.port }}"
  loop:
    - { role: nginx, port: 80 }
    - { role: redis, port: 6379 }
    - { role: postgresql, port: 5432 }
  loop_control:
    loop_var: service

Dynamic Role Names

# ✅ include_role allows variable role names
- ansible.builtin.include_role:
    name: "{{ db_engine }}_setup"
  vars:
    db_engine: "{{ database_type | default('postgresql') }}"

# ❌ import_role requires a literal role name
# - ansible.builtin.import_role:
#     name: "{{ db_engine }}_setup"  # FAILS at parse time

Tags Apply to the Include Task Only

# With include_role, tags apply to the include decision, not inner tasks
- ansible.builtin.include_role:
    name: nginx
  tags: webserver

# ansible-playbook site.yml --tags webserver
# → Runs ALL tasks in nginx role (the include itself is tagged) ✅

# ansible-playbook site.yml --tags config
# → Does NOT run nginx role (tag is on include, not inner tasks) ⚠️

Conditionals Apply Once

# when: decides whether to include the ENTIRE role
- ansible.builtin.include_role:
    name: nginx
  when: ansible_os_family == "Debian"

# If condition is false: role is skipped entirely (one "skipped" line)
# Compare with import_role: every task would show as "skipped"

Handler Differences

import_role: Handlers Immediately Available

# Role handlers are available from the start
- ansible.builtin.import_role:
    name: nginx

- name: Later task that triggers handler
  ansible.builtin.copy:
    src: custom.conf
    dest: /etc/nginx/conf.d/custom.conf
  notify: reload nginx    # ✅ Works — handler was loaded at parse time

include_role: Handlers Available After Include

# Handlers only available AFTER include_role runs
- name: Early task
  ansible.builtin.copy:
    src: custom.conf
    dest: /etc/nginx/conf.d/custom.conf
  notify: reload nginx    # ❌ May fail — handler not yet loaded

- ansible.builtin.include_role:
    name: nginx

- name: Late task
  ansible.builtin.copy:
    src: another.conf
    dest: /etc/nginx/conf.d/another.conf
  notify: reload nginx    # ✅ Works — handler loaded above

See also: Ansible Creator CLI: Scaffold Collections, Roles & Projects (v26.4.0)

Selective Task Loading

Both support loading specific task files:

# Only run the "install" tasks from a role
- ansible.builtin.import_role:
    name: nginx
    tasks_from: install

- ansible.builtin.include_role:
    name: nginx
    tasks_from: configure

# Also works with handlers and vars
- ansible.builtin.include_role:
    name: nginx
    handlers_from: custom_handlers
    vars_from: production
    defaults_from: custom_defaults

Decision Guide

Do you need to loop over roles?
└── YES → include_role

Do you need a dynamic/variable role name?
└── YES → include_role

Do you need --tags to filter tasks INSIDE the role?
└── YES → import_role

Do you need --list-tasks to show role contents?
└── YES → import_role

Do you need handlers available before the role runs?
└── YES → import_role

Is the role conditionally included based on runtime data?
└── YES → include_role (cleaner — one skip vs many skips)

None of the above?
└── import_role (more predictable, better tooling support)

See also: Ansible Development: Write Custom Modules, Plugins & Collections

Roles Section vs import/include

The traditional roles: section uses static loading (like import_role):

# These are equivalent:
- hosts: webservers
  roles:
    - nginx
    - { role: deploy_app, vars: { version: "2.0" } }

- hosts: webservers
  tasks:
    - ansible.builtin.import_role:
        name: nginx
    - ansible.builtin.import_role:
        name: deploy_app
      vars:
        version: "2.0"

FAQ

Can I mix import_role and include_role in the same playbook?

Yes. Use import_role for core roles where you need tag filtering and handler availability, and include_role for conditional or looped role loading. Mixing is common and supported.

Why can't import_role use variable names?

import_role loads at parse time, before variables are resolved. The role name must be a literal string so Ansible can find and parse the role's files before any task runs.

Which is faster?

import_role has slightly less runtime overhead because parsing happens once upfront. include_role re-parses each time it executes, which matters in loops. For a single role inclusion, the difference is negligible.

Does apply: work with include_role?

Yes. apply: lets you add tags, become, and other keywords to all tasks inside an included role:

- ansible.builtin.include_role:
    name: nginx
    apply:
      tags: webserver
      become: true

Conclusion

import_role (static) for predictable, tag-filterable role loading. include_role (dynamic) for loops, variable role names, and conditional loading. Default to import_role unless you need dynamic features — it has better tooling support and more predictable behavior.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home