file ModuleIn automated systems administration and infrastructure engineering, managing files, directories, symbolic links, and access control permissions is a fundamental operational task. Ansible provides the built-in ansible.builtin.file module to create, modify, and delete filesystem objects across remote managed nodes.
Unlike executing raw Linux shell commands (touch, mkdir, chmod, chown), the ansible.builtin.file module guarantees Idempotency. It inspects the target host’s filesystem before taking action:
changed: false).graph TD
subgraph ControlNode ["Jump Host (Ansible Control Node)"]
ThorUser["User: thor"]
InvFile["Inventory File<br/>/home/thor/ansible/inventory"]
PlaybookFile["Playbook File<br/>/home/thor/ansible/playbook.yml"]
AnsibleCLI["Ansible Engine<br/>ansible-playbook"]
end
subgraph StratosDC ["Stratos Datacenter (Managed Nodes)"]
subgraph AppServers ["Group: [app_servers]"]
App1["stapp01<br/>User: tony"]
App2["stapp02<br/>User: steve"]
App3["stapp03<br/>User: banner"]
end
File1["/opt/sysops/news.txt<br/>Owner: tony (0755)"]
File2["/opt/sysops/news.txt<br/>Owner: steve (0755)"]
File3["/opt/sysops/news.txt<br/>Owner: banner (0755)"]
end
InvFile -->|"Supply Hosts & ansible_user"| AnsibleCLI
PlaybookFile -->|"Supply Play & file module tasks"| AnsibleCLI
AnsibleCLI -->|"SSH (Port 22) - Resolve "| App1
AnsibleCLI -->|"SSH (Port 22) - Resolve "| App2
AnsibleCLI -->|"SSH (Port 22) - Resolve "| App3
App1 -->|"Execute state: touch"| File1
App2 -->|"Execute state: touch"| File2
App3 -->|"Execute state: touch"| File3
state Parameter)The ansible.builtin.file module operates according to the value assigned to its state parameter:
state: touch:
path.touch).state: directory:
path if they do not exist (equivalent to mkdir -p).state: absent:
path (equivalent to rm -rf).state: link & state: hard:
state: link) or hard links (state: hard) pointing from src to dest.state: file (Default):
mode), owner (owner), or group (group) on an existing file without modifying content or creating new files.When managing multi-tier server clusters where each server has a distinct administrative user (e.g., user tony on stapp01, steve on stapp02, banner on stapp03), hardcoding ownership in playbooks causes failures.
Ansible allows dynamic variable substitution using inventory variables:
owner: "": Resolves to the specific SSH username configured for each host in the inventory file.group: "": Dynamically assigns group ownership matching the host user.| Host Role | Hostname / Alias | IP Address | SSH User | Target File Path | Target Owner | Mode |
|---|---|---|---|---|---|---|
| Control Node | jump_host |
172.16.238.2 |
thor |
N/A | N/A | N/A |
| App Server 1 | stapp01 |
172.16.238.10 |
tony |
/opt/sysops/news.txt |
tony |
0755 |
| App Server 2 | stapp02 |
172.16.238.11 |
steve |
/opt/sysops/news.txt |
steve |
0755 |
| App Server 3 | stapp03 |
172.16.238.12 |
banner |
/opt/sysops/news.txt |
banner |
0755 |
/home/thor/ansible/inventory/home/thor/ansible/playbook.yml/opt/sysops/news.txt on all App Serversmode): 0755tony, steve, banner)become: yes enabledansible-playbook -i inventory playbook.ymlSSH into the Jump Host as user thor:
ssh thor@jump_host
Navigate to the Ansible project workspace directory:
cd /home/thor/ansible
Inspect /home/thor/ansible/inventory to ensure target hosts and connection variables are configured:
[app_servers]
stapp01 ansible_host=stapp01 ansible_user=tony ansible_ssh_pass=******
stapp02 ansible_host=stapp02 ansible_user=steve ansible_ssh_pass=******
stapp03 ansible_host=stapp03 ansible_user=banner ansible_ssh_pass=******
Test SSH connectivity across all application servers using an ad-hoc ping:
ansible app_servers -i inventory -m ping
Create /home/thor/ansible/playbook.yml using vi or nano:
vi /home/thor/ansible/playbook.yml
Add the following playbook code to ensure /opt/sysops/news.txt is created with proper ownership and permissions:
---
- name: Create news.txt file on all App Servers
hosts: app_servers
become: yes
tasks:
- name: Ensure target directory /opt/sysops exists
ansible.builtin.file:
path: /opt/sysops
state: directory
mode: '0755'
- name: Create empty file /opt/sysops/news.txt with host-specific ownership
ansible.builtin.file:
path: /opt/sysops/news.txt
state: touch
owner: ""
group: ""
mode: '0755'
[!TIP] Creating the parent directory
/opt/sysopsusingstate: directoryin a preliminary task ensures that the playbook does not fail if the directory is missing on a newly provisioned server.
Run syntax validation before running the playbook:
ansible-playbook -i inventory playbook.yml --syntax-check
Expected Output:
playbook: playbook.yml
Run the playbook against the inventory file:
ansible-playbook -i inventory playbook.yml
Expected Output:
PLAY [Create news.txt file on all App Servers] ***************************************************
TASK [Gathering Facts] ****************************************************************************
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [Ensure target directory /opt/sysops exists] *************************************************
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [Create empty file /opt/sysops/news.txt with host-specific ownership] ***********************
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
PLAY RECAP ****************************************************************************************
stapp01 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp02 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp03 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Run an ad-hoc shell command to confirm file path, permissions (0755), and owner/group resolution across all target nodes:
ansible app_servers -i inventory -m shell -a "ls -l /opt/sysops/news.txt"
Expected Output:
stapp01 | CHANGED | rc=0 >>
-rwxr-xr-x 1 tony tony 0 Aug 6 18:56 /opt/sysops/news.txt
stapp02 | CHANGED | rc=0 >>
-rwxr-xr-x 1 steve steve 0 Aug 6 18:56 /opt/sysops/news.txt
stapp03 | CHANGED | rc=0 >>
-rwxr-xr-x 1 banner banner 0 Aug 6 18:56 /opt/sysops/news.txt
Re-run the playbook to confirm idempotence (changed=0):
ansible-playbook -i inventory playbook.yml
Expected Output:
PLAY RECAP ****************************************************************************************
stapp01 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp02 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
stapp03 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
or to dynamically assign host-specific users and groups without hardcoding logic.state: directory before creating nested files to avoid missing path failures.mode: '0755') to prevent YAML parsers from converting octal numbers to decimal integers.ansible.builtin.file for file creation and metadata changes, and ansible.builtin.copy or ansible.builtin.template when writing actual file content.