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_tasks vs include_tasks: Key Differences (2026 Guide)

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

Complete guide to Ansible import_tasks vs include_tasks. Understand static vs dynamic task loading, when to use each, performance differences, and migration.

Ansible provides two ways to load external task files: import_tasks (static) and include_tasks (dynamic). They look similar but behave very differently at parse time vs runtime. Choosing the wrong one causes subtle bugs with tags, handlers, and conditionals.

Quick Comparison

# Static import — parsed at playbook load time
- name: Import database tasks
  ansible.builtin.import_tasks: database.yml

# Dynamic include — loaded at runtime when reached - name: Include database tasks ansible.builtin.include_tasks: database.yml

See also: Ansible delegate_to: Run Tasks on Different Hosts (Complete Guide)

Key Differences

| Feature | import_tasks (static) | include_tasks (dynamic) | |---------|------------------------|--------------------------| | When loaded | Playbook parse time | Runtime (when task is reached) | | Tags | Applied to all imported tasks | Only on the include task itself | | when condition | Applied to each imported task | Evaluated once before loading | | Handlers | Can notify imported handlers | Cannot notify included handlers | | Loops | ❌ Cannot use with loop | ✅ Can use with loop | | Variables | Cannot use task-level vars in filename | Can use runtime variables in filename | | --list-tasks | Shows imported tasks | Shows only the include task | | --list-tags | Shows tags from imports | Does not show include tags | | Error detection | Syntax errors caught early | Syntax errors caught at runtime |

import_tasks (Static)

Tasks are inserted into the play at parse time, as if you copied them directly into the playbook.

Basic Usage

# main.yml
- hosts: webservers
  tasks:
    - name: Import common setup
      ansible.builtin.import_tasks: tasks/common.yml

- name: Import web server config ansible.builtin.import_tasks: tasks/webserver.yml vars: http_port: 8080

# tasks/common.yml
- name: Update apt cache
  ansible.builtin.apt:
    update_cache: true
    cache_valid_time: 3600

- name: Install common packages ansible.builtin.apt: name: "{{ item }}" state: present loop: - curl - wget - vim

Tags with import_tasks

Tags cascade to ALL imported tasks:

- name: Import monitoring setup
  ansible.builtin.import_tasks: tasks/monitoring.yml
  tags: ['monitoring']
# ALL tasks in monitoring.yml now have the 'monitoring' tag
# ansible-playbook site.yml --tags monitoring  ← runs all of them

when with import_tasks

The when condition is applied to each imported task individually:

- name: Import Debian tasks
  ansible.builtin.import_tasks: tasks/debian.yml
  when: ansible_os_family == 'Debian'
# Each task in debian.yml gets: when: ansible_os_family == 'Debian'

❌ Cannot Use Loops

# This FAILS
- name: Import tasks in a loop
  ansible.builtin.import_tasks: "tasks/{{ item }}.yml"
  loop:
    - users
    - packages
# ERROR: import_tasks does not support loops

See also: Ansible Playbook Structure: Anatomy, Best Practices & Examples (2026)

include_tasks (Dynamic)

Tasks are loaded at runtime when the include task is reached.

Basic Usage

- hosts: all
  tasks:
    - name: Include OS-specific tasks
      ansible.builtin.include_tasks: "tasks/{{ ansible_os_family | lower }}.yml"

Dynamic Filenames

- name: Include tasks based on variable
  ansible.builtin.include_tasks: "tasks/{{ app_type }}/deploy.yml"
  vars:
    version: "{{ deploy_version }}"

- name: Include tasks based on fact ansible.builtin.include_tasks: "tasks/{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml"

Loops with include_tasks

- name: Configure each application
  ansible.builtin.include_tasks: tasks/configure_app.yml
  loop: "{{ applications }}"
  loop_control:
    loop_var: app
  vars:
    app_name: "{{ app.name }}"
    app_port: "{{ app.port }}"

Tags with include_tasks

Tags apply only to the include task itself, NOT to the tasks inside:

- name: Include monitoring
  ansible.builtin.include_tasks: tasks/monitoring.yml
  tags: ['monitoring']
# Only the include task has the tag
# The tasks INSIDE monitoring.yml do NOT get the tag
# ansible-playbook site.yml --tags monitoring  ← loads the file but tasks inside may not run

To make tags work with includes, add tags inside the included file:

# tasks/monitoring.yml
- name: Install monitoring agent
  ansible.builtin.package:
    name: node-exporter
  tags: ['monitoring']

Or use apply:

- name: Include monitoring
  ansible.builtin.include_tasks:
    file: tasks/monitoring.yml
    apply:
      tags: ['monitoring']
  tags: ['monitoring']  # Needed so the include itself runs when filtering by tag

When to Use Each

Use import_tasks When:

• You need tags to work across the included file • You need handlers defined in the imported file • The filename is static (known at parse time) • You want --list-tasks and --list-tags to show everything • You want early error detection (syntax errors caught before execution)
# Good use of import_tasks
- ansible.builtin.import_tasks: tasks/common.yml
  tags: ['common', 'setup']

- ansible.builtin.import_tasks: tasks/security.yml tags: ['security']

Use include_tasks When:

• The filename is dynamic (uses variables or facts) • You need to run includes in a loop • You want conditional loading (load different files based on conditions) • The included file might not exist (use ignore_errors) • You want tasks to load only when needed (performance)
# Good use of include_tasks
- ansible.builtin.include_tasks: "tasks/{{ ansible_os_family }}.yml"

- ansible.builtin.include_tasks: tasks/configure_app.yml loop: "{{ app_list }}"

See also: Ansible Tags: Run Specific Tasks and Roles Selectively (Guide)

Roles: import_role vs include_role

The same static/dynamic distinction applies to roles:

# Static (parsed at load time)
- name: Import webserver role
  ansible.builtin.import_role:
    name: webserver
  tags: ['web']

# Dynamic (loaded at runtime) - name: Include role based on variable ansible.builtin.include_role: name: "{{ server_role }}" vars: http_port: 8080

import_playbook

For including entire playbooks (only works at the play level, not inside tasks):

# site.yml
- ansible.builtin.import_playbook: webservers.yml
- ansible.builtin.import_playbook: databases.yml
- ansible.builtin.import_playbook: monitoring.yml

> Note: There is no include_playbook. Playbook inclusion is always static.

Common Patterns

OS-Specific Task Loading

# Recommended: include_tasks with dynamic filename
- name: Include OS-specific tasks
  ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml"

# Alternative: import with when - name: Import Debian tasks ansible.builtin.import_tasks: debian.yml when: ansible_os_family == 'Debian'

- name: Import RedHat tasks ansible.builtin.import_tasks: redhat.yml when: ansible_os_family == 'RedHat'

Role Structure

# roles/webserver/tasks/main.yml
- name: Import installation tasks
  ansible.builtin.import_tasks: install.yml
  tags: ['install']

- name: Import configuration ansible.builtin.import_tasks: configure.yml tags: ['configure']

- name: Include TLS setup (only if enabled) ansible.builtin.include_tasks: tls.yml when: enable_tls | default(false) tags: ['tls']

Common Mistakes

Tags Not Working with include_tasks

# Bug: --tags deploy doesn't run tasks inside deploy.yml
- ansible.builtin.include_tasks: deploy.yml
  tags: ['deploy']

# Fix: use import_tasks if you need tag filtering - ansible.builtin.import_tasks: deploy.yml tags: ['deploy']

# Or use apply with include_tasks - ansible.builtin.include_tasks: file: deploy.yml apply: tags: ['deploy'] tags: ['deploy']

Variable Filename with import_tasks

# Bug: fails because import is static
- ansible.builtin.import_tasks: "{{ os_family }}.yml"
# ERROR: variable not available at parse time

# Fix: use include_tasks for dynamic filenames - ansible.builtin.include_tasks: "{{ os_family }}.yml"

FAQ

What is the difference between import_tasks and include_tasks?

import_tasks is static — tasks are loaded at playbook parse time and behave as if written directly in the playbook. include_tasks is dynamic — tasks are loaded at runtime when the task is reached. This affects how tags, conditions, and loops work.

Can I use loops with import_tasks?

No, import_tasks does not support loops. Use include_tasks when you need to loop over a task file multiple times.

Why don't my tags work with include_tasks?

Tags on include_tasks only apply to the include task itself, not to the tasks inside the file. Use import_tasks for tag-based filtering, or add apply: with tags to pass them through to included tasks.

Which is better for performance?

include_tasks can be slightly better for large playbooks because tasks are only loaded when needed. import_tasks loads everything upfront. In practice, the difference is negligible for most playbooks.

Should I use import_tasks or include_tasks by default?

Use import_tasks as the default for static, predictable task loading. Switch to include_tasks when you need dynamic filenames, loops, or conditional file loading. If you use tags extensively, prefer import_tasks.

Conclusion

import_tasks — Static, parsed at load time. Tags and when cascade. No loops. Best for predictable structure. • include_tasks — Dynamic, loaded at runtime. Supports loops and variable filenames. Tags need special handling. • Rule of thumb: Use import_tasks unless you need dynamic behavior.

Related Articles

Ansible Roles: Create Reusable AutomationAnsible Handlers: Trigger Actions on ChangeAnsible Playbook Examples: Complete GuideAnsible Collections: Install, Use & Create

See also

Ansible import_role vs include_role: Static vs Dynamic Role Loading

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home