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

Featureimport_tasks (static)include_tasks (dynamic)
When loadedPlaybook parse timeRuntime (when task is reached)
TagsApplied to all imported tasksOnly on the include task itself
when conditionApplied to each imported taskEvaluated once before loading
HandlersCan notify imported handlersCannot notify included handlers
Loops❌ Cannot use with loop✅ Can use with loop
VariablesCannot use task-level vars in filenameCan use runtime variables in filename
--list-tasksShows imported tasksShows only the include task
--list-tagsShows tags from importsDoes not show include tags
Error detectionSyntax errors caught earlySyntax 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

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

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

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 }}"

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

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

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.

See also

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home