Execution Environments Demystified: Build Once, Run Anywhere, Pin Every Version
Why Execution Environments Exist
Ansible Execution Environments represent a modern approach to managing dependencies and running automation tasks. They've replaced the older method of using Python virtual environments.
Execution environments are OCI-compatible container images designed to run Ansible jobs. Unlike legacy virtual environments (directories on the filesystem containing Python packages), they are full-fledged container images. This shift lets you incorporate system-level dependencies alongside collection-based content, providing a more robust and isolated runtime for automation tasks.
OCI (Open Container Initiative) is an open governance structure for the express purpose of creating open industry standards around container formats and runtime. Its main purpose is to define what an open container is and to set a baseline standard around container formats and runtime. Docker, Podman, and other container runtimes are OCI-compliant.
Previously, Ansible jobs ran inside a Python virtual environment on the AWX controller, with a default venv at /var/lib/awx/venv/ansible that bundled ansible-runner and common Python libraries. Custom dependencies meant building extra venvs on every task node and keeping them in sync, which was fragile across upgrades. To replace that model, Ansible introduced execution environments: container images that package ansible-core, collections, Python libraries, and system dependencies into a single runtime. EE images are built with ansible-builder and run by ansible-runner, which lives inside the image itself.
Imagine your company runs automation against 3 database stacks (MySQL, PostgreSQL, and MongoDB) across 3 environments (dev, staging, prod). Each playbook needs the right client binaries (mysql, psql, mongosh) at specific versions, plus Python libraries like pymysql or psycopg2, plus the right Ansible collections.
Without execution environments, you'd have two bad options:
- Install every client and library on the AWX controller itself, then keep them in sync across upgrades and across multiple AWX nodes. The first time someone needs an older
psqlfor legacy prod and a newer one for dev, the controller can't satisfy both. - Maintain separate Python virtualenvs per project on the controller and hope they don't drift. This is what AWX used to do, and it broke constantly.
With execution environments, each combination of dependencies becomes its own container image. You build one EE for MySQL automation (mysql-client + pymysql + the community.mysql collection), another for Postgres, another for MongoDB. Each job template picks the EE it needs. The AWX controller stays clean. Upgrades don't touch your job runtimes. A playbook that worked yesterday still works tomorrow because the EE image is pinned by tag.
AWX in Action
Ansible Orchestration at ScaleEnroll now to unlock all content and receive all future updates for free.
