Introduction

When building Ansible playbooks that reference local files — templates, scripts, variable files, or custom modules — you need a reliable way to construct file paths that work regardless of where ansible-playbook is invoked from. The playbook_dir magic variable provides exactly this: the absolute filesystem path to the directory containing the playbook being executed.

This article covers playbook_dir usage, all related magic variables, practical patterns for portable path construction, and common pitfalls.

What Is playbook_dir?

playbook_dir is a magic variable (also called a special variable) that Ansible sets automatically. It contains the absolute path to the directory of the playbook that was passed to the ansible-playbook command.

# If you run:
# ansible-playbook /home/deploy/playbooks/site.yml

# Then:
# playbook_dir = "/home/deploy/playbooks"

Key characteristics:

  • Always available — no need to enable gather_facts
  • Set by Ansible itself — cannot be overridden in inventory or vars
  • Absolute path — always a full filesystem path
  • Directory only — does not include the playbook filename

Basic Usage

---
- name: Display playbook directory
  hosts: all
  gather_facts: false
  tasks:
    - name: Print playbook_dir
      ansible.builtin.debug:
        var: playbook_dir

Execution

$ ansible-playbook -i inventory playbooks/site.yml
PLAY [Display playbook directory] *********
TASK [Print playbook_dir] *****************
ok: [demo.example.com] => {
    "playbook_dir": "/home/deploy/playbooks"
}

Practical Use Cases

1. Reference Local Files Portably

- name: Copy script from playbook directory
  ansible.builtin.copy:
    src: "{{ playbook_dir }}/files/deploy.sh"
    dest: /opt/scripts/deploy.sh
    mode: "0755"

Without playbook_dir, the path would break if you cd to a different directory before running the playbook.

2. Dynamic Variable File Includes

- name: Load environment-specific variables
  ansible.builtin.include_vars:
    file: "{{ playbook_dir }}/vars/{{ env }}.yml"

3. Reference Templates Outside roles/

- name: Deploy config from shared templates
  ansible.builtin.template:
    src: "{{ playbook_dir }}/../shared/templates/app.conf.j2"
    dest: /etc/myapp/app.conf

4. Store Output Files Relative to Playbook

- name: Save inventory report
  ansible.builtin.copy:
    content: "{{ ansible_facts | to_nice_json }}"
    dest: "{{ playbook_dir }}/reports/{{ inventory_hostname }}.json"
  delegate_to: localhost

5. Include Tasks from Sibling Directories

# Directory structure:
# project/
# ├── playbooks/
# │   └── site.yml
# └── tasks/
#     └── common.yml

- name: Include shared tasks
  ansible.builtin.include_tasks: "{{ playbook_dir }}/../tasks/common.yml"

6. Custom Module or Plugin Path

# ansible.cfg can reference playbook_dir dynamically:
# But in practice, set it in ansible.cfg or via env var

- name: Run custom module
  my_custom_module:
    config: "{{ playbook_dir }}/configs/module_config.json"

All Ansible Magic Variables for Paths

VariableDescriptionExample
playbook_dirDirectory of the current playbook/home/deploy/playbooks
inventory_dirDirectory of the inventory file/home/deploy/inventory
inventory_fileFull path to the inventory file/home/deploy/inventory/hosts
role_pathPath to the current role directory/home/deploy/roles/webserver
ansible_config_filePath to the active ansible.cfg/home/deploy/ansible.cfg

Comparison: playbook_dir vs inventory_dir

- name: Show both paths
  ansible.builtin.debug:
    msg: |
      Playbook dir: {{ playbook_dir }}
      Inventory dir: {{ inventory_dir }}
      Inventory file: {{ inventory_file }}
# Running:
# ansible-playbook -i /etc/ansible/hosts playbooks/site.yml

# playbook_dir = "/home/deploy/playbooks"
# inventory_dir = "/etc/ansible"
# inventory_file = "/etc/ansible/hosts"

role_path Inside Roles

Within a role, role_path points to the role's root directory:

# roles/webserver/tasks/main.yml
- name: Show role path
  ansible.builtin.debug:
    var: role_path
    # Output: "/home/deploy/roles/webserver"

Other Useful Magic Variables

VariableDescription
inventory_hostnameCurrent host from inventory
inventory_hostname_shortShort hostname (before first dot)
ansible_play_nameName of the current play
ansible_play_hostsList of active hosts in current play
ansible_versionAnsible version info dict
groupsDict of all inventory groups
hostvarsDict of all host variables
group_namesGroups the current host belongs to
ansible_check_modeTrue if running in check mode
ansible_diff_modeTrue if running with --diff
- name: Show useful magic variables
  ansible.builtin.debug:
    msg: |
      Host: {{ inventory_hostname }}
      Play: {{ ansible_play_name }}
      Check mode: {{ ansible_check_mode }}
      Ansible version: {{ ansible_version.full }}
      Playbook dir: {{ playbook_dir }}

Building Portable Playbooks

Pattern: Project-Relative Paths

# Project structure:
# myproject/
# ├── ansible.cfg
# ├── inventory/
# ├── playbooks/
# │   ├── site.yml
# │   └── deploy.yml
# ├── files/
# ├── templates/
# └── vars/

# In playbooks/site.yml:
vars:
  project_root: "{{ playbook_dir }}/.."
  files_dir: "{{ playbook_dir }}/../files"
  templates_dir: "{{ playbook_dir }}/../templates"
  vars_dir: "{{ playbook_dir }}/../vars"

Pattern: Environment-Aware Config Loading

- name: Load environment config
  ansible.builtin.include_vars:
    file: "{{ playbook_dir }}/environments/{{ target_env }}/config.yml"

- name: Load secrets
  ansible.builtin.include_vars:
    file: "{{ playbook_dir }}/environments/{{ target_env }}/vault.yml"

Pattern: Dynamic Inventory Scripts

- name: Run inventory script from playbook dir
  ansible.builtin.command: "{{ playbook_dir }}/scripts/discover_hosts.py"
  register: discovered_hosts
  delegate_to: localhost
  changed_when: false

Common Pitfalls

1. playbook_dir Changes with Imported Playbooks

When using import_playbook, playbook_dir reflects the imported playbook's directory, not the importing one:

# /home/deploy/main.yml imports /home/deploy/sub/tasks.yml
# In tasks.yml: playbook_dir = "/home/deploy/sub" (not /home/deploy)

2. Relative Paths in Roles

Inside roles, Ansible automatically resolves paths relative to the role directory. You usually don't need playbook_dir for role files:

# roles/webserver/tasks/main.yml
- name: Deploy template
  ansible.builtin.template:
    src: nginx.conf.j2  # Automatically looks in roles/webserver/templates/
    dest: /etc/nginx/nginx.conf

3. playbook_dir Is Not the Working Directory

playbook_dir is where the playbook file lives, not where you ran ansible-playbook from:

# Working directory: /home/user
# Playbook location: /opt/ansible/playbooks/site.yml

$ ansible-playbook /opt/ansible/playbooks/site.yml
# playbook_dir = "/opt/ansible/playbooks"  (NOT /home/user)

4. Cannot Override playbook_dir

# ❌ This does NOT work — magic variables cannot be set by users
vars:
  playbook_dir: "/custom/path"

# The variable will still contain the real playbook directory

Debugging Path Issues

- name: Debug all path variables
  ansible.builtin.debug:
    msg: |
      playbook_dir: {{ playbook_dir }}
      inventory_dir: {{ inventory_dir | default('not set') }}
      inventory_file: {{ inventory_file | default('not set') }}
      role_path: {{ role_path | default('not in a role') }}
      ansible_config_file: {{ ansible_config_file | default('not set') }}

Conclusion

The playbook_dir magic variable is essential for building portable, path-independent playbooks. Use it to construct reliable file references for templates, scripts, variable files, and reports. Combined with inventory_dir, role_path, and other magic variables, you can create playbook projects that work correctly regardless of where they are cloned or executed from. Always use absolute path construction with playbook_dir instead of relying on the shell working directory.