Introduction
The ansible.builtin.file lookup plugin reads the contents of a file from the Ansible controller (the machine running ansible-playbook) and returns it as a string. This is useful for loading configuration snippets, certificates, SSH keys, or any file content into variables without copying the file to remote hosts first. This article covers basic usage, common patterns, error handling, and the difference between the file lookup and the slurp module.
How It Works
The file lookup runs on the controller, not on remote hosts. It reads the file during playbook parsing (for vars:) or task execution (for set_fact/debug) and returns the content as a string.
Controller (localhost) Remote Host
┌─────────────────┐ ┌──────────────┐
│ ansible-playbook│ │ │
│ │ │ │
│ lookup('file', │ │ │
│ 'data.txt') │──read──→ data.txt (local)
│ │ │ │
│ sends content │──SSH──→ │ uses content │
│ to remote task │ │ │
└─────────────────┘ └──────────────┘
Basic Usage
Read a File into a Variable
---
- name: Read file on controller
hosts: all
vars:
file_contents: "{{ lookup('file', 'example.txt') }}"
tasks:
- name: Display file contents
ansible.builtin.debug:
msg: "Content: {{ file_contents }}"
Read with Absolute Path
vars:
ssh_key: "{{ lookup('file', '/home/deploy/.ssh/id_rsa.pub') }}"
ssl_cert: "{{ lookup('file', '/etc/ssl/certs/myapp.crt') }}"
Read Relative to Playbook
File lookup searches relative to the playbook directory, then the files/ directory:
project/
├── playbook.yml
├── files/
│ └── config.txt ← found by lookup('file', 'config.txt')
└── config.txt ← also found (playbook dir searched first)
# Both work — searches playbook dir, then files/ dir
vars:
config: "{{ lookup('file', 'config.txt') }}"
Common Use Cases
1. Deploy SSH Authorized Keys
- name: Add SSH key to remote user
ansible.posix.authorized_key:
user: deploy
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state: present
2. Load SSL Certificates
- name: Deploy SSL certificate
ansible.builtin.copy:
content: "{{ lookup('file', 'certs/myapp.crt') }}"
dest: /etc/ssl/certs/myapp.crt
mode: "0644"
- name: Deploy SSL private key
ansible.builtin.copy:
content: "{{ lookup('file', 'certs/myapp.key') }}"
dest: /etc/ssl/private/myapp.key
mode: "0600"
no_log: true
3. Load Configuration Snippets
- name: Add custom nginx config
ansible.builtin.copy:
content: "{{ lookup('file', 'nginx/custom.conf') }}"
dest: /etc/nginx/conf.d/custom.conf
mode: "0644"
notify: Restart nginx
4. Read Multiple Files
- name: Read multiple config files
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ lookup('file', 'file1.txt', 'file2.txt', 'file3.txt', wantlist=True) }}"
5. Load JSON or YAML Data
# Read JSON file and parse it
vars:
app_config: "{{ lookup('file', 'config.json') | from_json }}"
# Read YAML file and parse it
db_config: "{{ lookup('file', 'database.yml') | from_yaml }}"
# Use parsed data
- name: Create database
community.postgresql.postgresql_db:
name: "{{ app_config.database.name }}"
encoding: "{{ app_config.database.encoding }}"
6. Inject File Content into Templates
# In a Jinja2 template (nginx.conf.j2):
# server {
# ssl_certificate_key {{ ssl_key }};
# }
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
vars:
ssl_key: "{{ lookup('file', 'certs/server.key') }}"
7. Set Facts from File Content
- name: Load version from file
ansible.builtin.set_fact:
app_version: "{{ lookup('file', 'VERSION') | trim }}"
- name: Deploy versioned artifact
ansible.builtin.get_url:
url: "https://releases.example.com/app-{{ app_version }}.tar.gz"
dest: /opt/app/
Error Handling
File Not Found
By default, a missing file causes a fatal error:
# This fails if secret.txt doesn't exist
vars:
secret: "{{ lookup('file', 'secret.txt') }}"
Handle Missing Files Gracefully
# Use default filter
vars:
config: "{{ lookup('file', 'custom.conf', errors='ignore') | default('# no custom config') }}"
# Check if file exists first
- name: Check for optional config
ansible.builtin.stat:
path: "{{ playbook_dir }}/files/custom.conf"
register: custom_config
delegate_to: localhost
- name: Load custom config if exists
ansible.builtin.set_fact:
extra_config: "{{ lookup('file', 'custom.conf') }}"
when: custom_config.stat.exists
Errors Parameter
# errors='strict' (default) — fail on missing file
# errors='warn' — warn but return empty string
# errors='ignore' — silently return empty string
vars:
optional: "{{ lookup('file', 'optional.txt', errors='warn') }}"
file Lookup vs slurp Module
| Feature | lookup('file') | ansible.builtin.slurp |
|---|---|---|
| Runs on | Controller | Remote host |
| Use case | Read local files | Read remote files |
| Returns | String | Base64-encoded string |
| Timing | Parse time (vars) or task time | Task time only |
| Network | No transfer needed | Transfers over SSH |
Read Remote File with slurp
# Use slurp to read files from remote hosts
- name: Read remote config
ansible.builtin.slurp:
src: /etc/nginx/nginx.conf
register: remote_config
- name: Display remote file
ansible.builtin.debug:
msg: "{{ remote_config.content | b64decode }}"
Read Local File with file Lookup
# Use file lookup to read files from the controller
- name: Display local file
ansible.builtin.debug:
msg: "{{ lookup('file', '/etc/hosts') }}"
Related Lookup Plugins
| Plugin | Purpose |
|---|---|
ansible.builtin.file | Read file contents |
ansible.builtin.template | Read and render Jinja2 template |
ansible.builtin.fileglob | Return list of files matching a pattern |
ansible.builtin.csvfile | Read data from CSV files |
ansible.builtin.ini | Read data from INI files |
ansible.builtin.lines | Read output of command as lines |
ansible.builtin.url | Read content from a URL |
# Template lookup — renders Jinja2 before returning
vars:
rendered: "{{ lookup('template', 'config.j2') }}"
# Fileglob — list matching files
tasks:
- name: Copy all configs
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/myapp/conf.d/
loop: "{{ lookup('fileglob', 'configs/*.conf', wantlist=True) }}"
Best Practices
- Use relative paths — place files in
files/directory for clean organization - Handle missing files — use
errors='warn'ordefault()for optional files - Parse structured data — use
| from_jsonor| from_yamlfor data files - Use
no_log: truewhen reading sensitive files (keys, passwords) - Prefer
copymodule overfilelookup +copy content:when the file doesn't need transformation - Use
slurpwhen you need to read files from remote hosts
Related Articles
- Ansible Variables Guide
- Ansible template Module
- Ansible copy Module Guide
- Ansible Jinja2 Templates Guide
Conclusion
The ansible.builtin.file lookup plugin reads files from the controller machine into playbook variables. Use it for SSH keys, SSL certificates, configuration snippets, and structured data (JSON/YAML). For remote file reading, use the slurp module instead. Handle missing files with errors='warn' or the default() filter, and always use no_log: true when working with sensitive content.