← All TILs · ansible

Running playbooks locally in the execution environment production uses

ansible - 2026-09-30

Seventh entry in the Ansible development environment series. Part 6 pointed the editor at the team's execution environment (EE), the container image that AAP (Red Hat Ansible Automation Platform) and its automation controller run every job in. This part does the same for the terminal. The aim is Alex Dworjan's rule: run the playbook locally, in that same image, before pushing, rather than pushing, syncing the project in controller, and finding out there.

The tool is ansible-navigator, part of ADT. By default it runs ansible-playbook inside an EE instead of on the host. Everything below was run with ansible-navigator 26.9.0 and Docker against the public ghcr.io/ansible-community/community-ee-base image. That image carries ansible-core 2.21.3 and three collections: ansible.posix, ansible.utils and ansible.windows.

Commit an ansible-navigator.yml

Navigator reads ansible-navigator.yml from the project directory, so the choice of image travels with the repository:

---
ansible-navigator:
  execution-environment:
    enabled: true
    container-engine: auto
    image: ghcr.io/ansible-community/community-ee-base@sha256:9f2836592ab92794e8b1982311d504ba3c28a2c09b3f8de221ca3564842c0902
    pull:
      policy: missing
    environment-variables:
      pass:
        - DEMO_TOKEN
  mode: stdout
  playbook-artifact:
    enable: false

What runs where

A playbook that prints where it is:

- hosts: localhost
  gather_facts: false
  tasks:
    - ansible.builtin.debug:
        msg:
          - "ansible-core {{ ansible_version.full }}"
          - "playbook_dir {{ playbook_dir }}"
          - "config {{ ansible_config_file }}"
          - "DEMO_TOKEN={{ lookup('ansible.builtin.env', 'DEMO_TOKEN') | default('unset', true) }}"
          - "OTHER_VAR={{ lookup('ansible.builtin.env', 'OTHER_VAR') | default('unset', true) }}"

Run with DEMO_TOKEN=abc123 OTHER_VAR=xyz ansible-navigator run where.yml:

"ansible-core 2.21.3",
"playbook_dir <project>",
"config <project>/ansible.cfg",
"DEMO_TOKEN=abc123",
"OTHER_VAR=unset"

The EE decides which collections exist

The same image has no community.general. A playbook using community.general.json_query failed in the EE with:

No filter named 'community.general.json_query'.

That's the useful failure: it's the one controller would give. There's one way to hide it locally. Ansible also loads collections from a collections/ directory next to the playbook. After ansible-galaxy collection install community.general -p ./collections, the same run found the filter, and ansible-navigator collections --mode stdout listed community.general 13.4.0 from the project beside the EE's three. But it failed one step later:

You need to install "jmespath" prior to running json_query filter

A collection copied into the project brings its code, not its Python dependencies. The EE has no jmespath, and the host venv's copy doesn't count. AWX's docs describe the controller-side equivalent: "If you specify a collections requirements file in SCM at collections/requirements.yml of a project, then AWX will install collections from that file" when the project updates. That installs collections only, into the job, so the same missing-library error would follow. A collection that needs Python libraries belongs in the EE, where ansible-builder installs its requirements too (part 3).

Before pushing

  1. ansible-navigator collections --mode stdout shows what the EE, plus anything in ./collections, actually provides.
  2. ansible-navigator run site.yml --check runs in the production image against a test inventory. --check is Ansible's dry run.
  3. Push when it passes. What's left to differ in controller is what the EE can't reproduce: controller's inventory, credentials and survey answers.

In the editor, Dworjan notes that the first time the Ansible extension switches to an EE, it pulls the image and copies its plugin docs. Highlighting may need a window reload to catch up.

Sources

Created 2026-09-30T00:05:51+02:00 · Edit