What Is a Lookup?
Every filter in the manipulating-data section transformed a value you already had. This section is about something different: fetching data from somewhere else entirely — a file, an environment variable, the output of a local command — and handing it back as something you can actually use. That’s a lookup, and the single most important thing to understand about it is where it actually runs.
Filters Transform Data You Already Have. Lookups Fetch Data From Somewhere Else.
Every filter you’ve used — upper, select, combine, all of them — operates on a value already sitting in a variable. A lookup doesn’t transform anything; it goes and gets something that wasn’t in a variable at all, from outside the playbook entirely.
Basic lookup() Syntax
Lookups are called as a genuine function, not piped like a filter:
msg: "{{ lookup('file', '/etc/hostname') }}"lookup('plugin_name', argument) — the first argument names which lookup plugin to use, and the rest are whatever that specific plugin needs.
The Big One: Lookups Run On The Control Node
Here’s the conceptual anchor for this entire section, worth reading twice: every module task in this course, by default, runs on the remote managed host — ubuntu, fedora, wherever hosts: points. A lookup runs on the control node — the machine actually running ansible/ansible-playbook — completely independent of whichever host a task is nominally targeting. It executes locally, before anything about that value is even sent to the remote host at all.
Proving It Concretely
- name: Compare Control Node vs. Managed Host
hosts: servers
tasks:
- name: Show the remote host's own hostname fact
ansible.builtin.debug:
msg: "Remote host fact: {{ ansible_facts['hostname'] }}"
- name: Show the control node's own hostname file via lookup
ansible.builtin.debug:
msg: "Control node file: {{ lookup('file', '/etc/hostname') }}"ok: [ubuntu] => { "msg": "Remote host fact: ubuntu" }
ok: [ubuntu] => { "msg": "Control node file: my-laptop" }
ok: [fedora] => { "msg": "Remote host fact: fedora" }
ok: [fedora] => { "msg": "Control node file: my-laptop" }The first message correctly differs per host — each one’s own real hostname. The second message is identical on both hosts — because it never actually touched either remote host at all; it read /etc/hostname on the control node, every single time, regardless of which host the task appeared to be running against. Nothing else demonstrated in this entire course behaves like that — every fact, every registered result, every variable you’ve seen so far genuinely varies by host. A lookup’s result doesn’t, because it was never fetched from a host in the first place.
Why This Matters
Since a lookup only ever sees the control node’s own files, environment, and locally-runnable commands, it has no access whatsoever to anything on a remote managed host. A common early mistake is expecting lookup('file', ...) to read a file that exists on the remote host — it won’t, because the lookup never goes anywhere near it. Reading a file’s contents from a remote host requires an actual task running there (a module built for that purpose, briefly worth knowing exists even without covering it here) — not a lookup, which is fundamentally a control-node-only tool.
Best Practices
- Keep the control-node/managed-host distinction sharp at all times — a task’s module runs remotely by default; a lookup runs locally, always, with no exceptions.
- Use lookups only for genuinely local, control-node-side data — local files, local environment variables, local command output — never data that lives on a remote host.
- Reach for
lookup()’s function-call syntax when you need to fetch new data from outside your existing variables, and reserve filters for transforming data you already have.