Import Unless: Prefer Static Reuse in Ansible
import_tasks, import_role, or the play-level roles section for reusable Ansible content. Use include_tasks or include_role only when you need dynamic runtime behavior.Categories:
import_tasks, import_role, or the
play-level roles section by default. Use include_tasks or include_role
only when you need dynamic runtime behavior such as looping, runtime file
selection, or conditional inclusion based on facts or earlier task results.Problem
Ansible offers both import_* and include_* forms for reusing tasks and
roles.
Without a clear rule, codebases drift into a mixture of static and dynamic
reuse. That makes playbooks harder to read, reason about, and troubleshoot.
Tasks may no longer appear clearly in --list-tasks, tags may behave
unexpectedly, and execution can depend on runtime state in ways that are harder
for reviewers and operators to see up front.
Context
According to the Ansible documentation, import_* is static reuse and is
processed while Ansible parses the playbook. The imported tasks or role tasks
then become part of the play as if they had been written there directly.
This gives practical advantages:
- Better visibility in
--list-tasksand--list-tags. - Better support for
--start-at-task. - Simpler tag behavior because tags apply to imported child tasks directly.
- More predictable structure because the task graph is known before execution.
- Lower runtime overhead, because imports are leaner than includes.
The Ansible documentation explicitly notes that imports are pre-processed during playbook parsing, while includes are processed dynamically at runtime. It also notes that imports are leaner and faster, while includes require more management and accounting.
The Red Hat COP guidance also shows a preference for static structure where that
is sufficient. It recommends using either the roles section or the tasks
section, not both, and shows import_role as the simpler tagging model when a
playbook is composed from roles.
Solution
- Use
import_tasksfor reusable task files by default. - Use
import_roleor the play-levelrolessection for reusable roles by default. - Use
include_tasksorinclude_roleonly when you need dynamic behavior at runtime. - Treat dynamic inclusion as an exception that must be justified by the use case, not as the normal style.
- When you need static role execution at play level, prefer the
rolessection. When you need a role in task order inside thetaskssection, useimport_role. - Avoid mixing static and dynamic reuse in the same playbook unless there is a clear reason.
Benefits
- Makes the play structure visible earlier.
- Improves readability and reviewability.
- Makes task listing, tag listing, and task targeting more useful.
- Reduces surprises caused by runtime-only expansion.
- Usually performs better than dynamic includes.
When include_* Is the Right Choice
Use include_tasks or include_role when the playbook genuinely needs dynamic
runtime behavior.
Typical cases are:
- Looping over a task file or role
include_*can run once per loop item.import_*cannot be used in a loop. - Selecting a task file at runtime
For example, provider-specific or platform-specific task files chosen with
lookup('first_found')or a fact-derived filename. - Including content only when runtime state demands it For example, when the inclusion decision depends on facts, registered results, or changes made by earlier tasks in the same play.
- Dynamic role orchestration patterns For example, a loop over actions where each item feeds variables into an included role.
- Specific handler patterns that rely on notifying the include itself With dynamic includes, the include task is what exists at runtime.
If none of these cases apply, use import.
Additional Guidance for Roles
For roles there are three common options:
- Use the play-level
rolessection for normal static role composition. - Use
import_rolewhen the role must appear in a specific place inside thetaskssequence. - Use
include_roleonly when you need dynamic behavior such as looping or runtime selection.
Do not use include_role merely out of habit. If the role name and its place in
execution are already known when the playbook is written, static composition is
clearer.
Examples and Implementation
Prefer import_tasks for fixed task structure
- name: Main tasks
hosts: all
gather_facts: false
tasks:
- name: Import install tasks
ansible.builtin.import_tasks: install.yml
- name: Import configure tasks
ansible.builtin.import_tasks: configure.yml
This is the normal choice when both task files are known in advance.
Use include_tasks for runtime file selection
- name: Main tasks
hosts: all
gather_facts: true
tasks:
- name: Include platform-specific tasks
ansible.builtin.include_tasks: >-
{{ lookup('first_found', params) }}
vars:
params:
files:
- "{{ ansible_facts['distribution'] }}.yml"
- "{{ ansible_facts['os_family'] }}.yml"
- default.yml
paths:
- "{{ role_path }}/tasks/setup"
Here the selected file is not fixed until runtime, so include_tasks is the
right tool.
Prefer import_role for fixed role execution in task order
- name: Configure servers
hosts: all
gather_facts: false
tasks:
- name: Import base role
ansible.builtin.import_role:
name: c2platform.core.base
- name: Import application role
ansible.builtin.import_role:
name: c2platform.app.web
This keeps the role execution static and visible.
Use include_role when looping over role inputs
- name: Execute actions
hosts: all
gather_facts: false
tasks:
- name: Run action role per item
ansible.builtin.include_role:
name: mycollection.run
vars:
action: "{{ item }}"
loop: "{{ actions }}"
This is a valid reason to use include_role, because the role is executed once
per loop item.
Prefer the play-level roles section for simple static composition
- name: Build web server
hosts: web
gather_facts: false
roles:
- role: c2platform.core.base
- role: c2platform.web.apache
This is often the clearest option when no dynamic behavior is needed.
Additional Information
- Reusing Ansible artifacts — Ansible Community Documentation
- import_tasks — Ansible Community Documentation
- include_tasks — Ansible Community Documentation
- import_role — Ansible Community Documentation
- include_role — Ansible Community Documentation
- Roles — Ansible Community Documentation
- Good Practices for Ansible — Red Hat COP
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.