Import Unless: Prefer Static Reuse in Ansible

Prefer 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.

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-tasks and --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

  1. Use import_tasks for reusable task files by default.
  2. Use import_role or the play-level roles section for reusable roles by default.
  3. Use include_tasks or include_role only when you need dynamic behavior at runtime.
  4. Treat dynamic inclusion as an exception that must be justified by the use case, not as the normal style.
  5. When you need static role execution at play level, prefer the roles section. When you need a role in task order inside the tasks section, use import_role.
  6. 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:

  1. Looping over a task file or role include_* can run once per loop item. import_* cannot be used in a loop.
  2. 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.
  3. 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.
  4. Dynamic role orchestration patterns For example, a loop over actions where each item feeds variables into an included role.
  5. 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 roles section for normal static role composition.
  • Use import_role when the role must appear in a specific place inside the tasks sequence.
  • Use include_role only 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


Last modified September 16, 2026: guideline imprort-unless C2-1638 (ef5cea9)