How to Create a Directory in Ansible

Creating directories is one of the most common Ansible tasks. The ansible.builtin.file module with state: directory creates single or nested directories, sets ownership and permissions, and is idempotent — running it again changes nothing if the directory already exists.

The shortest reliable pattern is ansible.builtin.file with path: /your/directory and state: directory; add owner, group, and mode when the directory needs specific permissions.

Basic Syntax

- name: Create a directory
  ansible.builtin.file:
    path: /opt/myapp
    state: directory

Parameters

ParameterDefaultDescription
path(required)Directory path to create
statefileSet to directory to create a directory
owner—Owner user name or UID
group—Group name or GID
mode—Permissions (e.g., "0755", "u=rwx,g=rx,o=rx")
recursefalseApply owner/group/mode recursively to contents
selevel—SELinux level
setype—SELinux type

Create a Single Directory

---
- name: Create application directory
  hosts: all
  become: true
  tasks:
    - name: Create /opt/myapp
      ansible.builtin.file:
        path: /opt/myapp
        state: directory
        owner: appuser
        group: appgroup
        mode: "0755"

Create Nested Directories

state: directory creates parent directories automatically (like mkdir -p):

- name: Create nested path (parents created automatically)
  ansible.builtin.file:
    path: /opt/myapp/releases/v2.1.0/config
    state: directory
    owner: deploy
    mode: "0755"
# Creates: /opt/myapp, /opt/myapp/releases, /opt/myapp/releases/v2.1.0, and /opt/myapp/releases/v2.1.0/config

Create Multiple Directories

---
- name: Create application directory structure
  hosts: all
  become: true
  vars:
    app_root: /opt/myapp
    directories:
      - "{{ app_root }}/config"
      - "{{ app_root }}/data"
      - "{{ app_root }}/logs"
      - "{{ app_root }}/tmp"
      - "{{ app_root }}/backups"
      - "{{ app_root }}/scripts"

  tasks:
    - name: Create all directories
      ansible.builtin.file:
        path: "{{ item }}"
        state: directory
        owner: appuser
        group: appgroup
        mode: "0755"
      loop: "{{ directories }}"

Set Permissions

---
- name: Create directories with specific permissions
  hosts: all
  become: true
  tasks:
    # Standard application directory
    - name: Create app directory (owner rwx, group rx, other rx)
      ansible.builtin.file:
        path: /opt/myapp
        state: directory
        owner: deploy
        group: deploy
        mode: "0755"

    # Restricted config directory
    - name: Create config directory (owner only)
      ansible.builtin.file:
        path: /opt/myapp/config
        state: directory
        owner: root
        group: root
        mode: "0700"

    # Shared writable directory
    - name: Create shared data directory with setgid
      ansible.builtin.file:
        path: /opt/myapp/shared
        state: directory
        owner: root
        group: appgroup
        mode: "2775"  # setgid — new files inherit group

    # World-readable log directory
    - name: Create log directory
      ansible.builtin.file:
        path: /var/log/myapp
        state: directory
        owner: appuser
        group: adm
        mode: "0755"

    # Temp directory with sticky bit
    - name: Create temp directory with sticky bit
      ansible.builtin.file:
        path: /opt/myapp/tmp
        state: directory
        mode: "1777"  # sticky bit — only owner can delete files

Check If Directory Exists

---
- name: Conditional directory operations
  hosts: all
  tasks:
    - name: Check if directory exists
      ansible.builtin.stat:
        path: /opt/myapp
      register: app_dir

    - name: Show result
      ansible.builtin.debug:
        msg: "Directory exists: {{ app_dir.stat.exists and app_dir.stat.isdir }}"

    - name: Create only if missing
      ansible.builtin.file:
        path: /opt/myapp
        state: directory
      when: not app_dir.stat.exists

Note: The stat check is usually unnecessary — file with state: directory is already idempotent. Use stat only when you need conditional logic based on existence.

Apply Permissions Recursively

- name: Fix permissions on entire directory tree
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
    owner: deploy
    group: deploy
    recurse: true  # Applies to all files and subdirectories

⚠️ recurse: true sets the same owner/group/mode on all contents. If files and directories need different permissions, use find + file instead:

- name: Find all directories
  ansible.builtin.find:
    paths: /opt/myapp
    file_type: directory
    recurse: true
  register: dirs

- name: Set directory permissions (755)
  ansible.builtin.file:
    path: "{{ item.path }}"
    mode: "0755"
  loop: "{{ dirs.files }}"

- name: Find all files
  ansible.builtin.find:
    paths: /opt/myapp
    file_type: file
    recurse: true
  register: files

- name: Set file permissions (644)
  ansible.builtin.file:
    path: "{{ item.path }}"
    mode: "0644"
  loop: "{{ files.files }}"

Practical Patterns

Web Application Directory Structure

---
- name: Set up web application
  hosts: webservers
  become: true
  vars:
    app: mywebapp
    user: www-data

  tasks:
    - name: Create application directories
      ansible.builtin.file:
        path: "{{ item.path }}"
        state: directory
        owner: "{{ item.owner | default(user) }}"
        group: "{{ user }}"
        mode: "{{ item.mode | default('0755') }}"
      loop:
        - { path: "/var/www/{{ app }}" }
        - { path: "/var/www/{{ app }}/public" }
        - { path: "/var/www/{{ app }}/storage", mode: "0775" }
        - { path: "/var/www/{{ app }}/storage/logs", mode: "0775" }
        - { path: "/var/www/{{ app }}/storage/cache", mode: "0775" }
        - { path: "/var/www/{{ app }}/config", owner: "root", mode: "0750" }
        - { path: "/var/log/{{ app }}", owner: "root" }

Temporary Directory with Cleanup

- name: Create temp build directory
  ansible.builtin.file:
    path: /tmp/build-{{ ansible_date_time.epoch }}
    state: directory
    mode: "0700"
  register: build_dir

- name: Use the directory
  ansible.builtin.debug:
    msg: "Building in {{ build_dir.path }}"

- name: Clean up when done
  ansible.builtin.file:
    path: "{{ build_dir.path }}"
    state: absent

Troubleshooting

"Permission denied"

You need become: true to create directories outside the Ansible user's home:

- name: Create system directory
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
  become: true

Directory Created with Wrong Permissions

Quote the mode as a string to prevent YAML from interpreting it as an octal number:

# Wrong — YAML parses 0755 as integer 493
mode: 0755

# Right — string, interpreted correctly
mode: "0755"

"chown failed: failed to look up user"

The owner user doesn't exist on the target. Create the user first:

- name: Create app user
  ansible.builtin.user:
    name: appuser
    system: true

- name: Create directory owned by appuser
  ansible.builtin.file:
    path: /opt/myapp
    state: directory
    owner: appuser

Conclusion

Create directories with ansible.builtin.file and state: directory. Parent directories are created automatically. Always quote mode as a string ("0755" not 0755). Use recurse: true to apply ownership changes to all contents, but prefer find + file when files and directories need different permissions. The module is idempotent — it only makes changes when the directory is missing or permissions differ.