Introduction
Running Ansible consistently across teams, CI/CD pipelines, and production requires isolation. Three approaches exist: Python virtualenvs, Docker containers, and Ansible Execution Environments (EE). Each solves different problems at different scales. This guide compares all three with practical setup examples.
Quick Comparison
| Feature | virtualenv | Docker | Execution Environment |
|---|---|---|---|
| Isolation level | Python packages | Full OS + packages | Full OS + Python + collections |
| System packages | ❌ Host only | ✅ Custom OS packages | ✅ Custom OS packages |
| Ansible collections | Manual install | Manual install | ✅ Built-in, versioned |
| Build tool | python -m venv | Dockerfile | ansible-builder |
| Runtime | source bin/activate | docker run | ansible-navigator or AAP |
| CI/CD integration | ⚠️ Fragile | ✅ Standard | ✅ Standard |
| AAP/AWX compatible | ❌ No | ❌ Not directly | ✅ Native |
| Team sharing | requirements.txt | Docker Hub / registry | Registry (Quay, GHCR) |
virtualenv — Quick Python Isolation
# Create virtual environment
python3 -m venv ~/ansible-env
source ~/ansible-env/bin/activate
# Install Ansible + dependencies
pip install ansible-core==2.20.0
pip install jmespath netaddr passlib
# Install collections
ansible-galaxy collection install community.general amazon.aws
# Freeze versions
pip freeze > requirements.txt
ansible-galaxy collection list --format yaml > requirements.yml
# requirements.txt
ansible-core==2.20.0
jmespath==1.0.1
netaddr==1.3.0
passlib==1.7.4
# requirements.yml
collections:
- name: community.general
version: "9.0.0"
- name: amazon.aws
version: "7.0.0"
When virtualenv works
# ✅ Single developer / small team
# ✅ No system-level dependencies (C libraries, CLI tools)
# ✅ Quick experimentation
# ✅ All team members use same OS
# ❌ Doesn't isolate: system packages, C libraries, OS differences
# ❌ "Works on my machine" — macOS vs Ubuntu vs RHEL
Docker — Full OS Isolation
# Dockerfile.ansible
FROM python:3.12-slim
RUN apt-get update && apt-get install -y \
openssh-client \
sshpass \
git \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir \
ansible-core==2.20.0 \
jmespath \
netaddr
COPY requirements.yml /tmp/requirements.yml
RUN ansible-galaxy collection install -r /tmp/requirements.yml
WORKDIR /ansible
ENTRYPOINT ["ansible-playbook"]
# Build
docker build -f Dockerfile.ansible -t ansible-runner .
# Run
docker run --rm \
-v $(pwd):/ansible \
-v ~/.ssh:/root/.ssh:ro \
ansible-runner site.yml -i inventory/production
CI/CD with Docker
# .github/workflows/ansible.yml
jobs:
deploy:
runs-on: ubuntu-latest
container:
image: ghcr.io/myorg/ansible-runner:2.20
steps:
- uses: actions/checkout@v4
- run: ansible-playbook site.yml -i inventory/production
Execution Environments — Ansible-Native Containers
# execution-environment.yml
---
version: 3
dependencies:
galaxy: requirements.yml
python: requirements.txt
system: bindep.txt
images:
base_image: quay.io/ansible/ansible-runner:latest
additional_build_steps:
prepend_base:
- RUN whoami
append_final:
- RUN ansible --version
# requirements.yml
collections:
- community.general>=9.0.0
- amazon.aws>=7.0.0
- kubernetes.core>=3.0.0
# requirements.txt
jmespath>=1.0.1
netaddr>=1.3.0
boto3>=1.34.0
kubernetes>=28.0.0
# bindep.txt (system packages)
gcc [compile]
python3-devel [compile]
libxml2-devel [platform:redhat]
# Build the EE
pip install ansible-builder
ansible-builder build --tag myorg/ansible-ee:1.0.0
# Run with ansible-navigator
pip install ansible-navigator
ansible-navigator run site.yml -i inventory/production \
--execution-environment-image myorg/ansible-ee:1.0.0 \
--mode stdout
# Push to registry
docker push myorg/ansible-ee:1.0.0
ansible-navigator
# Interactive mode (TUI)
ansible-navigator run site.yml --eei myorg/ansible-ee:1.0.0
# Stdout mode (CI-friendly)
ansible-navigator run site.yml --eei myorg/ansible-ee:1.0.0 -m stdout
# Explore EE contents
ansible-navigator collections --eei myorg/ansible-ee:1.0.0
ansible-navigator images
AAP / AWX Integration
# In AAP/AWX, EEs are first-class:
# 1. Push EE to container registry
# 2. Add registry credential in AAP
# 3. Create EE pointing to your image
# 4. Assign EE to job templates
# → Every job runs in your exact environment
Decision Guide
Single developer, same OS?
├─ Yes → virtualenv (simplest)
└─ No ↓
Need system packages or C libraries?
├─ Yes ↓
└─ No → virtualenv + requirements.txt
Using AAP/AWX?
├─ Yes → Execution Environment (required for AAP 2.x)
└─ No ↓
Team + CI/CD needs reproducibility?
├─ Yes → Docker or Execution Environment
└─ No → virtualenv
Common Mistakes
# ❌ Installing Ansible globally
sudo pip install ansible # Pollutes system Python!
# ✅ Always use isolation
python3 -m venv ~/ansible-env
# ❌ Not pinning versions in EE
# requirements.yml
collections:
- community.general # Gets latest — may break!
# ✅ Pin versions
collections:
- name: community.general
version: ">=9.0.0,<10.0.0"
# ❌ Using Docker when EE exists (reinventing the wheel)
# Dockerfile with manual ansible-galaxy install, pip install...
# ✅ Use ansible-builder — it handles all of this
ansible-builder build --tag myorg/ansible-ee:1.0.0
Related Articles
Conclusion
virtualenv for solo developers or quick experiments — isolates Python but not the OS. Docker for CI/CD and team standardization — full OS isolation with familiar tooling. Execution Environments for enterprise and AAP — purpose-built for Ansible with collections, Python deps, and system packages in one versioned image. If you're using AAP 2.x, EEs are required. If you're running in CI/CD, either Docker or EEs work. If you're a solo dev, start with virtualenv and graduate to EEs when you need reproducibility.