Ansible OEL 8 DevOps · OEL 8 · Fundamentals

AnsibleImports vs Includes — Static or Dynamic

Why Ansible has two ways to pull in external task files, when each one wins, and the subtle differences in how tags, when, and loops behave that bite people who pick the wrong one.

Ansible offers two ways to break a playbook into reusable parts:

  • import_*static. The included file's contents are inlined at parse time, before any task runs.
  • include_*dynamic. The file is loaded and executed when control reaches the include task at runtime.

Both look similar but behave very differently when combined with tags, conditionals, or loops.

import_* (static) include_* (dynamic) When? at parse time, before any task runs tags, when, loop applied to ALL imported tasks at parse Use when… file path is known at write time faster, simpler debugging When? at runtime, when the include task is reached tags, when, loop applied to the include itself, not to inner tasks Use when… file path uses a variable resolved at runtime flexible · loops over files
DirectiveWhat it pulls inStatic / Dynamic
import_playbookAn entire playbook file (only at the top level)Static
import_tasksA list of tasks from another fileStatic
import_roleA role's tasks/handlers/vars at this point in the playStatic
include_tasksA list of tasks from another fileDynamic
include_roleA role's tasks at runtimeDynamic
include_varsVariables from a YAML/JSON fileDynamic
YAML — import_tasks example
---
- hosts: all
  tasks:
    - name: OS hardening tasks
      ansible.builtin.import_tasks: harden.yml
      tags: [hardening]

At parse time Ansible reads harden.yml and inlines all of its tasks. The tags: [hardening] attribute is applied to every imported task, so any of them can be run individually with --tags hardening.

YAML — include_tasks with a variable in the path
---
- hosts: all
  tasks:
    - name: OS-specific setup
      ansible.builtin.include_tasks: "setup-{{{{ ansible_distribution }}}}.yml"
      # this can't be import_tasks — the path uses a fact that doesn't exist yet at parse time

At runtime, Ansible evaluates the variable, finds setup-OracleLinux.yml, and runs the tasks from it. Tags on this include_tasks apply only to the include itself — not to inner tasks (because they don't exist at tag-parse time).

Only include_tasks can loop. import_tasks doesn't support loop::

YAML — looping include_tasks
- name: Configure each tenant
  ansible.builtin.include_tasks: tenant-setup.yml
  vars:
    tenant: "{{{{ item }}}}"
  loop:
    - acme
    - globex
    - initech

# tenant-setup.yml is run THREE times, with tenant set to each value
Behaviourimport_*include_*
Resolved when?Parse timeRuntime
Path can use variables?NoYes
Tags apply to inner tasks?YesNo (only to the include itself)
Can be looped?NoYes
Notify handlers in another file?YesYes (for include_tasks; trickier for include_role)
Faster?Slightly — parsed onceSlightly slower (per-iteration parse)
Easier to debug?Yes — flat task listNo — dynamic resolution hides tasks
  1. Need to use a variable in the path or in a loop? → include_*
  2. Need --tags to filter inner tasks individually? → import_*
  3. Just splitting one big file into smaller files? → import_* (static is simpler)
  4. Generating tasks based on facts gathered at runtime? → include_*

import_playbook is special — it can only appear at the top level of a playbook file, not inside tasks:. It chains multiple playbooks together:

YAML — site.yml stitching
---
# site.yml — top-level orchestrator
- import_playbook: 01-os-prep.yml
- import_playbook: 02-database-tier.yml
- import_playbook: 03-web-tier.yml
- import_playbook: 04-smoke-tests.yml

# Run the whole thing:
#   ansible-playbook site.yml
# Or just one:
#   ansible-playbook 02-database-tier.yml
✅ Tip: Use import_playbook for top-level orchestration, import_tasks for splitting big task files, and include_tasks only when you genuinely need runtime variability. That covers 95% of real-world reuse.

You've now covered the full playbook surface area: structure, variables and precedence, group_vars/host_vars layout, Jinja2 templating, conditionals, loops, handlers, blocks, error handling, and the import vs include distinction. With these in your toolkit you can read and write any production playbook.

Next up — Section 3: Roles and Reuse (6 pages, 20–25). You'll learn how to package those scattered tasks into reusable roles, manage role dependencies, distribute them through Ansible Galaxy, and structure a library of roles that scales across teams.