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

| Feature | import_role (static) | include_role (dynamic) | |---------|---------------------|----------------------| | When loaded | Parse time (before play starts) | Runtime (when task executes) | | Tags | Inherited by all tasks in role | Applied to include task only | | Handlers | Available globally | Available after include runs | | Loop support | ❌ No | ✅ Yes | | Conditional (when) | Applied to every task in role | Applied once to include decision | | Variable file loading | Immediate | Deferred | | --list-tasks | Shows role tasks | Shows "include_role: name=X" | | --list-tags | Shows role tags | Hidden | | Error on missing | Fails at parse time | Fails at runtime |

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

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)

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"

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

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

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

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

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)

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.

Related Articles

Ansible import_tasks vs include_tasksAnsible Roles: Complete GuideAnsible Tags GuideAnsible Playbook Structure Guide

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home