Day 85: Create Files on App Servers using Ansible | 100 Days of DevOps
Content
Today I worked on an Ansible automation task that involved creating a file on multiple application servers in the Stratos Datacenter. This exercise helped me understand how Ansible manages files on remote systems, uses inventory variables dynamically, applies permissions and ownership settings, and performs idempotent operations across multiple hosts.
๐น What I Learned
- Creating and managing Ansible inventory files
- Using host-specific variables within an inventory
- Creating files on remote servers using the Ansible file module
- Managing file ownership and permissions
- Understanding Ansible's idempotent behavior
- Validating file creation across multiple servers
๐น Task Requirement
As per the Nautilus DevOps team requirements, I needed to:
Create an inventory file:
~/playbook/inventory
and add all application servers as managed nodes.
Create a playbook:
~/playbook/playbook.yml
to create a blank file:
/opt/webdata.txt
on all application servers.
Ensure:
File permissions are set to
0644Owner and group are:
tonyon stapp01steveon stapp02banneron stapp03
The solution must work using:
ansible-playbook -i inventory playbook.yml
without requiring any additional arguments.
๐น Steps I Followed
1. Navigated to the Working Directory
cd ~/playbook
Checked the directory contents:
ls
2. Created the Inventory File
Opened the inventory file:
vi inventory
Added the following content:
[app_servers]
stapp01 ansible_host=stapp01 ansible_user=tony ansible_password=Ir0nM@n file_mode='0644'
stapp02 ansible_host=stapp02 ansible_user=steve ansible_password=Am3ric@ file_mode='0644'
stapp03 ansible_host=stapp03 ansible_user=banner ansible_password=BigGr33n file_mode='0644'
Saved the file.
๐น Understanding the Inventory
Inventory Group
[app_servers]
Creates a host group named:
app_servers
This allows the playbook to target all application servers at once.
Managed Hosts
stapp01
stapp02
stapp03
These are the servers Ansible will manage.
Authentication Variables
Example:
ansible_user=tony
ansible_password=Ir0nM@n
These values tell Ansible:
- Which user account to use
- Which password to authenticate with
during SSH connections.
Custom Host Variable
Each host also contains:
file_mode='0644'
This variable can be referenced directly within the playbook.
Complete Inventory
[app_servers]
stapp01 ansible_host=stapp01 ansible_user=tony ansible_password=Ir0nM@n file_mode='0644'
stapp02 ansible_host=stapp02 ansible_user=steve ansible_password=Am3ric@ file_mode='0644'
stapp03 ansible_host=stapp03 ansible_user=banner ansible_password=BigGr33n file_mode='0644'
๐ In simple terms:
This inventory tells Ansible:
- Which servers to manage
- Which users to connect with
- Which passwords to use
- Which hosts belong to the application server group
- Which file permission should be applied
3. Verified Connectivity
Before creating the playbook, I tested connectivity:
ansible app_servers -i inventory -m ping
Output:
stapp01 | SUCCESS => pong
stapp02 | SUCCESS => pong
stapp03 | SUCCESS => pong
๐น Why This Step Is Important
This confirms:
- Inventory is configured correctly
- SSH authentication works
- Ansible can reach all target servers
- Python is available on remote hosts
Performing this validation helps prevent troubleshooting later.
4. Verified the Initial State
Before running any automation, I connected to one application server.
ssh tony@stapp01
Checked whether the target file existed:
ls -l /opt/webdata.txt
Output:
ls: cannot access '/opt/webdata.txt': No such file or directory
Observation:
The file did not exist before deployment.
This served as the baseline state before running the playbook.
5. Created the Playbook
Opened the playbook file:
vi playbook.yml
Added the following content:
---
- name: create file on app servers
hosts: app_servers
become: true
tasks:
- name: create file with custom owner and mode
ansible.builtin.file:
path: /opt/webdata.txt
state: touch
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: "{{ file_mode }}"
Saved the file.
๐น Understanding the Playbook
Target Hosts
hosts: app_servers
The playbook runs on every host inside the app_servers inventory group.
In this case:
- stapp01
- stapp02
- stapp03
Privilege Escalation
become: true
Allows Ansible to execute tasks using elevated privileges.
This is required because the destination location is:
/opt
which typically requires root permissions.
File Module
ansible.builtin.file
Used to manage files, directories, ownership, and permissions on remote systems.
Creating the File
state: touch
Functions similarly to:
touch /opt/webdata.txt
If the file does not exist, it is created.
If it already exists, Ansible updates its timestamp without recreating it.
Dynamic Ownership Assignment
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
Ansible automatically uses the username defined in the inventory for each host.
Result:
| Host | Owner | Group |
|---|---|---|
| stapp01 | tony | tony |
| stapp02 | steve | steve |
| stapp03 | banner | banner |
This eliminates the need for separate tasks for each server.
File Permissions
mode: "{{ file_mode }}"
The permission value comes directly from the inventory.
Inventory:
file_mode='0644'
Equivalent Linux command:
chmod 644 /opt/webdata.txt
Permission breakdown:
| User Type | Permission |
|---|---|
| Owner | Read, Write |
| Group | Read |
| Others | Read |
6. Executed the Playbook
Ran:
ansible-playbook -i inventory playbook.yml
Output:
PLAY [create file on app servers]
TASK [Gathering Facts]
ok: [stapp01]
ok: [stapp02]
ok: [stapp03]
TASK [create file with custom owner and mode]
changed: [stapp01]
changed: [stapp02]
changed: [stapp03]
PLAY RECAP
stapp01 : ok=2 changed=1 failed=0
stapp02 : ok=2 changed=1 failed=0
stapp03 : ok=2 changed=1 failed=0
๐น Understanding the Output
Gathering Facts
TASK [Gathering Facts]
Ansible collects system information before running tasks.
Examples include:
- Hostname
- Operating system details
- Network information
- Python interpreter location
File Creation Task
changed
This indicates that Ansible modified the server state.
Since the file did not previously exist, Ansible created it successfully.
Play Recap
ok=2
changed=1
failed=0
Meaning:
- Two tasks completed successfully
- One task modified the server
- No failures occurred
7. Validated the Deployment
Connected back to App Server 1:
ssh tony@stapp01
Checked the file:
ls -l /opt/webdata.txt
Output:
-rw-r--r-- 1 tony tony 0 Jun 24 16:06 /opt/webdata.txt
Verification:
โ File exists
โ Correct permissions applied
โ Correct owner assigned
โ Correct group assigned
โ File size is 0 bytes (blank file)
๐น Why Dynamic Variables Are Useful
One of the most useful concepts reinforced during this task was the use of inventory variables inside the playbook.
Instead of hardcoding:
owner: tony
for every server, I used:
owner: "{{ ansible_user }}"
This allowed Ansible to automatically assign the correct owner based on each host's inventory definition.
Benefits:
- Less code
- Easier maintenance
- Better scalability
- Reduced duplication
๐น My Understanding
This task strengthened my understanding of how Ansible can create and manage files across multiple servers while dynamically applying different ownership settings based on inventory variables. I learned how inventories can store host-specific data and how playbooks can reuse that information to build flexible and scalable automation.
๐น What I Found Interesting
I found it interesting that a single task was able to create the file, assign permissions, and set the correct ownership on three different servers without requiring separate configurations for each host. Using inventory variables together with Jinja2 expressions made the playbook clean, reusable, and easy to manage.
๐ Full notes: GitHub link