unit 9 · Workplace ecosystem

Execution environments & navigator

Docker eats the control node

essential · 7 min · linux + windows

you may want to read 3 · ansible-core vs the community package , 26 · Galaxy, requirements.yml & pinning first — but nothing is locked

tl;dr

  • An EE is a container image: ansible-core + collections + their Python/system deps, built by ansible-builder.
  • ansible-navigator runs playbooks inside an EE by default — the image tag pins your whole runtime.
  • Inside an EE, localhost is the container: files "saved locally" vanish when the container is removed.

why it matters AWX/AAP shops run everything inside EE containers — assume collections live on the control node and you’ll install fixes in the wrong place and lose files to a container that no longer exists.

Lesson 3 left you an IOU: in AWX and AAP, neither install model applies — collections are baked into an execution-environment container image. This is where it gets paid. An EE is Docker doing to Ansible what you’ve watched it do to application code.

First, the disease. The model you’ve run since Unit 2 — pip install ansible on a control node, collections pinned through lesson 26’s requirements.yml — pins collection versions and nothing else. The control node still has a Python, an ansible-core, a set of system libraries. No requirements file governs those. Two engineers and a CI runner can satisfy the same requirements.yml and still be three different machines. That’s “works on my machine”. A virtualenv shrinks the blast radius but doesn’t close it.

An execution environment — “EE” in every standup — closes it the way you closed it for apps: put the whole runtime in a container image. One image bundles ansible-core, the collections, and their Python and system dependencies. This is the standard runtime model in AWX/AAP. Jobs there don’t run on the controller’s own Python. They run inside an EE, replacing “install collections on the control node” outright. Unit 2’s setup was for learning. This is its industrialized successor. ansible-builder builds the images: it reads a definition file (base image, collections, Python and system deps) and emits the image.

Running inside the image: ansible-navigator

Meet ansible-navigator , the modern CLI/TUI. Its run subcommand executes a playbook inside an EE by default — it pulls the image and runs there, not with your laptop’s Python:

# run in the default EE image — a TUI opens unless you ask for plain output
ansible-navigator run site.yml -i inventories/prod --mode stdout

# pin the exact runtime: the same image tag your AWX/AAP jobs use
ansible-navigator run site.yml --execution-environment-image registry.example.com/company-ee:1.4
same engine, containerized runtime

That second line is the whole point. Local runs and controller runs resolve to the same image, so “works on my machine” stops being a sentence anyone says. The other subcommands interrogate the image, not your machine:

ansible-navigator collections            # what does the image actually ship?
ansible-navigator doc ansible.windows.win_service   # docs from the EE's collections
ansible-navigator exec                   # a shell inside the EE — docker exec energy
interrogating the image, not your laptop

Plain ansible-playbook isn’t deprecated and you’ll keep seeing it daily. Navigator wraps the same engine. “Run it through navigator” means “run it in the blessed image”.

The localhost trap

- name: Save an audit report "locally"
  hosts: localhost
  tasks:
    - name: Write the report
      ansible.builtin.copy:
        content: '{{ audit_summary }}'
        dest: /tmp/audit.txt # under navigator: inside the container, gone after the run
worked for years on a control node — silently vanishes in an EE

What recognition means here

Nobody expects you to build EEs in week one. Teams keep one or a few blessed images in a registry. Someone owns the definition file. Adding a collection means rebuilding the image and bumping the tag — not running ansible-galaxy on a server. Your day-one bar is smaller:

  • “EE” / “the image” in a standup = the execution-environment container, and the tag decides every version in play.
  • Know navigator exists and that run uses an EE by default, so a playbook command that isn’t ansible-playbook doesn’t throw you.
  • Know the localhost trap, or your first local navigator run will find it for you.

One cert note: the current RHCE exam (EX294) expects you to run playbooks with ansible-navigator and execution environments — lesson 42 picks up the cert story. Nearer to hand is Jenkins, where your new team’s pipelines actually invoke Ansible. A pipeline can install Ansible on the agent or run it through the plugin. Either way you now speak the runtime vocabulary it assumes. That’s next.

at work, this sounds like

“Which EE does that run in?”
Which container image and tag supplies the runtime — it fixes the ansible-core, collection and Python versions the playbook gets.
“Add the collection to the EE and rebuild.”
Collections aren’t installed on a control node here — ansible-builder bakes them into a new image version, and the tag gets bumped.

self-check — recall, not a test

  1. 1 A teammate says “it works in my venv but fails in AWX.” Where do you look first, and why?

  2. 2 Your playbook writes a report to /tmp on localhost. Fine under ansible-playbook, but after a navigator run the file is gone. Why?

  3. 3 requirements.yml and an EE both “pin versions.” What does each pin, and which pins more?

next up 41 · Jenkins + Ansible 7 min · unit 9