---
# 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`, and `synchronize`.
## 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
---
- Complete Lab 004 to understand how playbooks and tasks are structured.
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"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: truetasks:
# 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
- tempThe 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 nginxtasks:
# 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"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: absenttasks:
# 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 }}/-
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
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
