Why Ansible Execution Environments Replace Virtualenvs for Reliable Automation
Execution Environments package Ansible, collections, and system dependencies into a single container image, eliminating drift across dev, CI, and production.
16 Jun 2026, 14:30 UTC

The drift problem
You write a playbook that runs perfectly on your laptop. The same playbook fails in CI because the community.general collection version differs, or a system library (like libselinux) is missing. The classic fix — pinning versions in requirements.yml and rebuilding a virtualenv on every runner — works until a new developer joins or a base OS image updates.
Execution Environments (EE) solve this by packaging Ansible Core, Python, every collection, and any system packages into a single OCI image. The same image digest runs on a developer’s workstation, in GitLab CI, and on an Automation Controller (AWX/AAP) node. No host‑side pip or ansible-galaxy steps are required at runtime.
Building an EE with ansible‑builder
Start with an execution-environment.yml that declares what you need. The file replaces the old requirements.yml + requirements.txt combo.
# execution-environment.yml
version: 3
images:
base_image:
name: registry.access.redhat.com/ubi9/python-311:latest
dependencies:
python:
- requests
system:
- python3-libselinux
galaxy:
- collection: community.general
version: "6.5.0"
Run the builder (requires ansible-builder and a container runtime such as Podman). The command below creates a local image tagged local/ee-demo:0.1.
$ ansible-builder build -t local/ee-demo:0.1 -f execution-environment.yml
# Expected: a Containerfile is generated, the image is built, and the final digest is printed.
Verification: inspect the generated Containerfile (ansible-builder create) to confirm the base image, package manager (dnf for UBI), and collection install steps. Then check layer size:
$ podman image history local/ee-demo:0.1
# Look for a total size in the 300‑500 MB range (base + collections + system pkgs).
Running playbooks inside the EE
With the image ready, use ansible-navigator (CLI) to execute a playbook without installing anything on the host.
$ cat <<'EOF' > site.yml
- hosts: localhost
tasks:
- name: Show collection version
ansible.builtin.debug:
msg: "{{ community.general.version }}"
EOF
$ ansible-navigator run site.yml --eei local/ee-demo:0.1 --pull-policy missing
# The playbook runs inside the container; the project directory is mounted automatically.
If your playbook needs an SSH key, mount it at runtime:
$ ansible-navigator run site.yml \
--eei local/ee-demo:0.1 \
-v $HOME/.ssh/id_rsa:/home/runner/.ssh/id_rsa:Z
# The :Z label satisfies SELinux on RHEL/Fedora hosts.
Expected check: the job log should show Execution environment: local/ee-demo:0.1 and the debug task prints the pinned collection version 6.5.0.
Trade‑offs and limitations
- Image size: EE images add ~200‑500 MB over a minimal venv. On ephemeral CI runners with tight disk quotas, enable layer caching or use a shared registry mirror.
- Package manager coupling: System packages listed under
dependencies.systemmust match the base image’s manager (dnf for UBI, apt for Debian). Mixing them fails silently at build time. - Credential handling: Most modules read secrets from environment variables, but legacy plugins that expect files (e.g.,
~/.aws/credentials) need explicit volume mounts or init containers. - Controller EE assignment: In AWX/AAP, an EE is attached per job template or organization default. Workflow job nodes that omit an explicit EE silently fall back to the default, which can cause version drift across nodes.
- ansible‑builder 3.x: Requires Ansible Core ≥ 2.14. Collections pinned to older cores need a separate EE build pipeline or a shim base image.
Actionable next step
Pick one playbook that currently relies on a local virtualenv. Create a minimal execution-environment.yml for it, build the image with ansible-builder, and run the playbook via ansible-navigator. Verify the collection version in the output. If it works, promote that image to your CI registry and update the corresponding job template in Automation Controller. You’ll have a single, versioned artifact that guarantees the same runtime everywhere — no more “works on my machine” surprises.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.