File Modules¶
- In this lab we explore Ansible’s file management modules - the tools for copying, templating, modifying, and synchronizing files on managed hosts.
- File modules are among the most frequently used modules in real-world playbooks.
- We will cover
copy,fetch,file,template,lineinfile,blockinfile, andsynchronize.
What will we learn?¶
copy,fetch,file,template,lineinfile,blockinfile,synchronize- When to use each module and their key parameters
- How to set permissions, ownership, and use Jinja2 templates
Prerequisites¶
- Complete Lab 004 to understand how playbooks and tasks are structured.
01. copy Module - Transfer Files¶
tasks:
# Copy a local file to remote hosts
- name: Copy a local file
ansible.builtin.copy:
src: files/nginx.conf # Relative to playbook directory
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
backup: true # Create a backup before overwriting
# Copy with inline content
- name: Create a file with content
ansible.builtin.copy:
content: |
# Generated by Ansible
# Do not edit manually
server_name={{ inventory_hostname }}
port=8080
dest: /etc/myapp/config.ini
mode: "0600"
# Copy directory recursively
- name: Copy entire directory
ansible.builtin.copy:
src: files/website/ # Trailing slash copies contents
dest: /var/www/html/
mode: "0644"
directory_mode: "0755"
02. fetch Module - Download Files from Remote Hosts¶
tasks:
# Fetch a file from each managed host (creates subdirectories per host)
- name: Fetch log files
ansible.builtin.fetch:
src: /var/log/nginx/access.log
dest: logs/ # Creates: logs/<hostname>/var/log/nginx/access.log
flat: false
# Flat fetch (all files in one directory, renamed by hostname)
- name: Fetch hostname file
ansible.builtin.fetch:
src: /etc/hostname
dest: "fetched/{{ inventory_hostname }}-hostname"
flat: true
03. file Module - Manage File/Directory State¶
tasks:
# Create a directory
- name: Create application directory
ansible.builtin.file:
path: /opt/myapp
state: directory
mode: "0755"
owner: appuser
group: appgroup
# Create a file (empty)
- name: Create empty file
ansible.builtin.file:
path: /var/log/myapp.log
state: touch
mode: "0640"
# Create a symbolic link
- name: Create symlink
ansible.builtin.file:
src: /opt/myapp-v2.1
dest: /opt/myapp-current
state: link
# Delete a file or directory
- name: Remove old files
ansible.builtin.file:
path: /tmp/old-deployment
state: absent
# Create a directory tree using a loop
- name: Create nested directories
ansible.builtin.file:
path: /opt/app/{{ item }}
state: directory
mode: "0755"
loop:
- logs
- config
- data
- temp
04. template Module - Jinja2 Templates¶
The template module processes Jinja2 template files (.j2) and copies the rendered result to managed hosts.
{# templates/nginx.conf.j2 #}
worker_processes {{ ansible_processor_vcpus }};
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
}
http {
server_name {{ inventory_hostname }};
listen {{ http_port | default(80) }};
{% if ssl_enabled | default(false) %}
listen 443 ssl;
ssl_certificate /etc/ssl/{{ inventory_hostname }}.crt;
{% endif %}
{% for location in nginx_locations | default([]) %}
location {{ location.path }} {
proxy_pass {{ location.backend }};
}
{% endfor %}
}
tasks:
- name: Deploy nginx configuration
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
validate: /usr/sbin/nginx -t -c %s # Validate before replacing
notify: Reload nginx
05. lineinfile Module - Edit Single Lines¶
tasks:
# Ensure a line exists in a file
- name: Set max open files
ansible.builtin.lineinfile:
path: /etc/security/limits.conf
line: "* soft nofile 65536"
state: present
# Replace a line matching a pattern
- name: Set SSH port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?Port "
line: "Port 2222"
backup: true
notify: Restart sshd
# Remove a line
- name: Remove a line
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: "^192.168.1.100"
state: absent
# Insert after a specific line
- name: Insert after pattern
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
insertafter: "^Port"
line: "AllowUsers ansible admin"
06. blockinfile Module - Manage Blocks of Text¶
tasks:
# Insert a block of text
- name: Add hosts entries
ansible.builtin.blockinfile:
path: /etc/hosts
block: |
10.0.0.10 web1.internal
10.0.0.11 web2.internal
10.0.0.20 db1.internal
marker: "# {mark} ANSIBLE MANAGED BLOCK"
backup: true
# Remove a managed block
- name: Remove hosts entries
ansible.builtin.blockinfile:
path: /etc/hosts
block: ""
state: absent
07. synchronize Module - rsync Files¶
tasks:
# Sync a local directory to remote hosts
- name: Sync web content
ansible.posix.synchronize:
src: /local/website/
dest: /var/www/html/
delete: true # Remove files not in source
recursive: true
rsync_opts:
- "--exclude=.git"
- "--exclude=node_modules"
- "--compress"
# Pull files from remote to local
- name: Pull logs from server
ansible.posix.synchronize:
mode: pull
src: /var/log/nginx/
dest: /local/logs/{{ inventory_hostname }}/

08. Hands-on¶
- Create a
templates/directory inside the controller and write atemplates/config.ini.j2Jinja2 template that includesinventory_hostname,ansible_default_ipv4.address, and anenvvariable with a default oflab.
??? success “Solution”
docker exec ansible-controller sh -c "cd /labs-scripts && mkdir -p templates files"
docker exec ansible-controller sh -c "cd /labs-scripts && cat > templates/config.ini.j2 << 'EOF'
# Configuration for {{ inventory_hostname }}
# Generated by Ansible on {{ ansible_date_time.date }}
[server]
hostname = {{ inventory_hostname }}
ip_address = {{ ansible_default_ipv4.address }}
environment = {{ env | default('lab') }}
[logging]
level = {{ log_level | default('info') }}
path = /var/log/myapp.log
EOF"
- Write a playbook
lab016-files.ymlthat creates the directory structure/opt/myapp/{config,logs,data}on all hosts, deploysconfig.ini.j2to/opt/myapp/config/app.ini, and creates a log file with initial content.
??? success “Solution”
docker exec ansible-controller sh -c "cd /labs-scripts && cat > lab016-files.yml << 'EOF'
---
- name: File Modules Practice
hosts: all
gather_facts: true
vars:
env: lab
log_level: debug
tasks:
- name: Create application directory structure
ansible.builtin.file:
path: \"/opt/myapp/{{ item }}\"
state: directory
mode: \"0755\"
loop:
- config
- logs
- data
- name: Deploy configuration from template
ansible.builtin.template:
src: templates/config.ini.j2
dest: /opt/myapp/config/app.ini
mode: \"0644\"
- name: Create a log file
ansible.builtin.copy:
content: \"Application started\n\"
dest: /opt/myapp/logs/app.log
mode: \"0640\"
EOF"
docker exec ansible-controller sh -c "cd /labs-scripts && ansible-playbook lab016-files.yml"
- Add a task to
lab016-files.ymlthat useslineinfileto ensure the line127.0.0.1 myapp.localis present in/etc/hostson all servers.
??? success “Solution”
docker exec ansible-controller sh -c "cd /labs-scripts && ansible all -m lineinfile -a \"path=/etc/hosts line='127.0.0.1 myapp.local' state=present\" --become"
- Use the
fetchmodule to pull the generated/opt/myapp/config/app.inifrom all servers back to the controller underfetched/.
??? success “Solution”
docker exec ansible-controller sh -c "cd /labs-scripts && ansible all -m fetch -a 'src=/opt/myapp/config/app.ini dest=fetched/ flat=false'"
- Use
blockinfileto add a block of custom entries to/etc/hostson all servers, then verify the block was inserted.
??? success “Solution”
docker exec ansible-controller sh -c "cd /labs-scripts && ansible all -m blockinfile -a \"path=/etc/hosts block='10.0.0.10 web1.internal\n10.0.0.11 web2.internal' marker='# {mark} ANSIBLE MANAGED BLOCK'\" --become"
# Verify
docker exec ansible-controller sh -c "cd /labs-scripts && ansible all -m command -a 'grep -A5 ANSIBLE /etc/hosts'"
- Show the deployed config on all servers by reading
/opt/myapp/config/app.ini.
??? success “Solution”
docker exec ansible-controller sh -c "cd /labs-scripts && ansible all -m command -a 'cat /opt/myapp/config/app.ini'"
### Output
# Configuration for linux-server-1
# Generated by Ansible on 2026-03-17
#
# [server]
# hostname = linux-server-1
# ip_address = 172.20.0.2
# environment = lab
09. Summary¶
copytransfers files from the control node to managed hosts;fetchdoes the reversefilemanages the state of files, directories, and symlinks with a single moduletemplaterenders Jinja2 (.j2) files using host variables before copying - ideal for config fileslineinfilemakes precise single-line edits using regex matchingblockinfilemanages entire blocks of text with auto-generated markers for idempotencysynchronizeuses rsync for efficient bulk directory synchronization- Use
validate:intemplateandcopyto check files are valid before replacing them - Use
backup: trueto keep a copy of the original file before any change