Ansible Write to File: 5 Methods with Practical Examples (2026)
By Luca Berton · Published 2024-01-01 · Category: database-automation
Complete guide to writing content to files with Ansible. Use copy, template, lineinfile, blockinfile, and shell modules to create files, write variables.
Writing content to files is one of the most common tasks in Ansible automation. Whether you need to create configuration files, save command output, write variables to disk, or append lines to existing files, Ansible provides multiple modules for every scenario.
This guide covers five proven methods to write to files with Ansible, from simple string writes to complex template rendering.
Method 1: ansible.builtin.copy — Write String Content
The copy module with the content parameter is the simplest way to write text to a file.
Write a Simple String
- name: Write a string to a file
ansible.builtin.copy:
content: "Hello, World!\n"
dest: /tmp/hello.txt
mode: '0644'
Write a Variable to a File
- name: Gather facts and write hostname to file
ansible.builtin.copy:
content: "{{ ansible_hostname }}\n"
dest: /tmp/hostname.txt
mode: '0644'
- name: Write JSON data to file
ansible.builtin.copy:
content: "{{ my_variable | to_nice_json }}\n"
dest: /tmp/data.json
mode: '0644'
Write Multi-line Content
- name: Write multiple lines to a file
ansible.builtin.copy:
content: |
# Server Configuration
server_name={{ inventory_hostname }}
environment={{ env }}
deployed_at={{ ansible_date_time.iso8601 }}
dest: /etc/app/config.txt
mode: '0644'
Write Command Output to a File
- name: Run a command
ansible.builtin.command: df -h
register: disk_usage
changed_when: false
- name: Save command output to file
ansible.builtin.copy:
content: "{{ disk_usage.stdout }}\n"
dest: /tmp/disk_report.txt
mode: '0644'
When to use copy: Simple content writes, variable dumps, command output saves, single-file creation.
See also: ansible.builtin.file Module: Manage Files, Directories & Symlinks (Complete Guide)
Method 2: ansible.builtin.template — Jinja2 Templates
For complex file generation with loops, conditionals, and dynamic content, use the template module with a Jinja2 template file.
Template File (templates/config.j2)
# Generated by Ansible on {{ ansible_date_time.iso8601 }}
# Do not edit manually
{% for item in servers %}
server {{ item.name }} {{ item.ip }}:{{ item.port | default(8080) }}
{% endfor %}
{% if enable_ssl | default(false) %}
ssl_certificate /etc/ssl/certs/{{ domain }}.crt
ssl_certificate_key /etc/ssl/private/{{ domain }}.key
{% endif %}
Playbook Task
- name: Generate configuration from template
ansible.builtin.template:
src: templates/config.j2
dest: /etc/app/server.conf
owner: root
group: root
mode: '0644'
backup: yes
vars:
servers:
- name: web1
ip: 192.168.1.10
- name: web2
ip: 192.168.1.11
port: 9090
enable_ssl: true
domain: example.com
When to use template: Configuration files, dynamic content with loops/conditionals, any file that needs Jinja2 rendering.
Method 3: ansible.builtin.lineinfile — Write or Replace a Single Line
The lineinfile module ensures a specific line exists in a file, or replaces a matching line.
Add a Line to a File
- name: Ensure line exists in file
ansible.builtin.lineinfile:
path: /etc/environment
line: 'JAVA_HOME=/usr/lib/jvm/java-17-openjdk'
create: yes
Replace a Line Using Regex
- name: Update max connections in PostgreSQL config
ansible.builtin.lineinfile:
path: /etc/postgresql/15/main/postgresql.conf
regexp: '^max_connections\s*='
line: 'max_connections = 200'
backup: yes
Append After a Pattern
- name: Add DNS server after existing entries
ansible.builtin.lineinfile:
path: /etc/resolv.conf
insertafter: '^nameserver'
line: 'nameserver 8.8.8.8'
When to use lineinfile: Single-line changes, config file updates, ensuring a specific line exists.
See also: Ansible 'fatal: template error while templating string' Fix (Guide)
Method 4: ansible.builtin.blockinfile — Write a Block of Text
The blockinfile module manages a marked block of text within a file.
Add a Block to a File
- name: Add SSH config block
ansible.builtin.blockinfile:
path: /etc/ssh/sshd_config
marker: "# {mark} ANSIBLE MANAGED - Security Settings"
block: |
PermitRootLogin no
PasswordAuthentication no
MaxAuthTries 3
ClientAliveInterval 300
backup: yes
Dynamic Block with Variables
- name: Add host entries
ansible.builtin.blockinfile:
path: /etc/hosts
marker: "# {mark} ANSIBLE MANAGED BLOCK"
block: |
{% for host in groups['webservers'] %}
{{ hostvars[host]['ansible_default_ipv4']['address'] }} {{ host }}
{% endfor %}
When to use blockinfile: Multi-line inserts, managed config sections, content that needs to be updated as a unit.
Method 5: ansible.builtin.shell — Redirect Output to File
For complex scenarios or when you need shell features like pipes and redirects.
- name: Write process list to file
ansible.builtin.shell: ps aux --sort=-%mem | head -20 > /tmp/top_processes.txt
changed_when: true
- name: Append to a log file
ansible.builtin.shell: echo "Deploy completed at $(date)" >> /var/log/deploys.log
changed_when: true
> ⚠️ Best Practice: Prefer copy or template over shell for writing files. Shell commands are harder to make idempotent and don't support check mode.
See also: Ansible Collections: What They Are & How to Use Them (2026 Guide)
Comparison Table
| Feature | copy | template | lineinfile | blockinfile | shell | |---------|------|----------|------------|-------------|-------| | Create new file | ✅ | ✅ | ✅ (create: yes) | ✅ (create: yes) | ✅ | | Write string | ✅ | ✅ | Single line | Block | ✅ | | Write variable | ✅ | ✅ | ✅ | ✅ | ❌ | | Jinja2 rendering | In content | ✅ Full | In line | In block | ❌ | | Idempotent | ✅ | ✅ | ✅ | ✅ | ❌ | | Backup support | ✅ | ✅ | ✅ | ✅ | ❌ | | Check mode | ✅ | ✅ | ✅ | ✅ | ❌ | | Append mode | ❌ | ❌ | ✅ | ✅ | ✅ |
Best Practices
1. Always Set File Permissions
- name: Write sensitive config
ansible.builtin.copy:
content: "{{ db_password }}"
dest: /etc/app/db_secret
owner: appuser
group: appgroup
mode: '0600'
2. Use backup: yes for Critical Files
- name: Update config with backup
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
3. Validate Before Writing
- name: Write and validate nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
4. Use no_log for Sensitive Data
- name: Write secrets file
ansible.builtin.copy:
content: "{{ vault_api_key }}"
dest: /etc/app/api_key
mode: '0600'
no_log: true
Write Dictionary or List to File
- name: Write dictionary as YAML
ansible.builtin.copy:
content: "{{ my_dict | to_nice_yaml }}\n"
dest: /tmp/config.yml
mode: '0644'
- name: Write list as JSON
ansible.builtin.copy:
content: "{{ my_list | to_nice_json }}\n"
dest: /tmp/data.json
mode: '0644'
- name: Write list as one item per line
ansible.builtin.copy:
content: "{{ my_list | join('\n') }}\n"
dest: /tmp/items.txt
mode: '0644'
Write to Local File (Controller)
- name: Save inventory to local file
ansible.builtin.copy:
content: "{{ groups['all'] | join('\n') }}\n"
dest: /tmp/inventory_hosts.txt
mode: '0644'
delegate_to: localhost
run_once: true
- name: Write to local file using local_action
local_action:
module: ansible.builtin.copy
content: "{{ hostvars[inventory_hostname] | to_nice_json }}\n"
dest: "/tmp/facts_{{ inventory_hostname }}.json"
FAQ
What is the simplest way to write a string to a file in Ansible?
Use ansible.builtin.copy with the content parameter: copy: content="Hello" dest=/tmp/file.txt. This is idempotent — it only writes when content changes.
How do I write a variable to a file?
Use the copy module with Jinja2 syntax: copy: content="{{ my_variable }}" dest=/tmp/output.txt. For structured data, use to_nice_json or to_nice_yaml filters.
How do I append to a file instead of overwriting?
Use lineinfile to add a single line, blockinfile to add a block, or shell with >> redirect for simple appends. The copy and template modules always overwrite the entire file.
How do I write command output to a file?
Register the command output then write it: first command: df -h with register: result, then copy: content="{{ result.stdout }}" dest=/tmp/output.txt.
What is the difference between copy content and template?
copy with content is best for simple strings and variables. template uses a separate .j2 file with full Jinja2 support (loops, conditionals, includes) — better for complex configuration files.
How do I create a file only if it does not exist?
Use copy with force: no: copy: content="default" dest=/tmp/file.txt force=no. This creates the file with default content but never overwrites existing content.
Conclusion
Ansible provides five primary methods to write content to files: • copy — Best for simple string and variable writes • template — Best for complex files with Jinja2 logic • lineinfile — Best for single-line additions or replacements • blockinfile — Best for managed multi-line sections • shell — Last resort for complex shell operations
Choose the simplest module that meets your needs. Start with copy, upgrade to template when you need loops or conditionals, and use lineinfile/blockinfile for surgical edits to existing files.
Related Articles
• Ansible Write Variable to File: copy vs template Module Guide • Apply a File Template: Ansible Module template • Edit Single Line Text: Ansible Module lineinfile • Edit Multi-line Text: Ansible Module blockinfile • Copy Files to Remote Hosts: Ansible Module copy • Create a Text File: Ansible Module copyCategory: database-automation