Skip to main content

Command Palette

Search for a command to run...

Day 85: Create Files on App Servers using Ansible | 100 Days of DevOps

Updated
โ€ข7 min read
R
Iโ€™m currently working in DevOps and documenting my learning journey along the way. From CI/CD pipelines to cloud and containers, Iโ€™m exploring how modern systems are built and deployed. This blog is where I share what I learn, break down concepts in simple terms, and track my progress. Learning one concept at a time, and trying to apply it practically.

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 0644

  • Owner and group are:

    • tony on stapp01
    • steve on stapp02
    • banner on 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

100_days_of_devops

Part 1 of 50

Documenting my 100 Days of DevOps journey with hands-on practice from KodeKloud, along with notes, commands, and mini projects