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_tasks • Ansible Roles: Complete Guide • Ansible Tags Guide • Ansible Playbook Structure GuideCategory: installation