Introduction
Ansible Runner (ansible-runner) is a command-line tool that executes Ansible playbooks inside Execution Environments (EE) — container images that package Ansible, Python dependencies, and collections into a portable runtime. This replaces managing Python virtual environments manually and ensures your development and production environments are identical. This article covers installation, project structure, running playbooks in containers, and troubleshooting.
What Are Execution Environments?
Execution Environments are container images (OCI/Docker) that serve as Ansible control nodes. They contain:
- ansible-core (or full
ansiblepackage) - Python dependencies (boto3, pywinrm, etc.)
- Ansible collections (community.general, amazon.aws, etc.)
- System packages (gcc, openssl-devel, etc.)
┌─────────────────────────────┐
│ Execution Environment │
│ ┌───────────────────────┐ │
│ │ ansible-core 2.16+ │ │
│ │ Python 3.12 │ │
│ │ amazon.aws collection │ │
│ │ boto3, botocore │ │
│ │ community.general │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
Why Use EEs?
| Challenge | Without EE | With EE |
|---|---|---|
| Python conflicts | Manual venv management | Isolated container |
| Collection versions | Global install collisions | Per-EE pinned versions |
| Dev/Prod parity | "Works on my machine" | Same image everywhere |
| CI/CD | Complex setup scripts | docker pull + run |
| Team consistency | Each dev has different env | Shared container image |
Installation
Install ansible-runner
# Via pip
pip install ansible-runner
# On RHEL/CentOS with AAP subscription
sudo dnf install ansible-runner
# On Fedora
sudo dnf install ansible-runner
# Verify
ansible-runner --version
Install Container Runtime
ansible-runner needs podman or docker:
# Podman (preferred on RHEL/Fedora)
sudo dnf install podman
# Docker
sudo apt install docker.io # Debian/Ubuntu
# or
sudo dnf install docker-ce # RHEL/Fedora
Project Structure
ansible-runner expects a specific directory layout:
my-project/
├── project/
│ ├── site.yml # Your playbook
│ └── roles/
│ └── webserver/
├── inventory/
│ └── hosts # Inventory file
├── env/
│ ├── settings # Runner settings (JSON)
│ ├── envvars # Environment variables
│ ├── extravars # Extra variables
│ ├── cmdline # CLI arguments
│ └── ssh_key # SSH private key
└── artifacts/ # Output (auto-created)
└── <uuid>/
├── stdout
├── stderr
├── rc
└── fact_cache/
Basic Usage
Run a Playbook Locally (No Container)
# Simple run
ansible-runner run /path/to/project -p site.yml
# With inventory
ansible-runner run . -p site.yml --inventory inventory/hosts
Run Inside an Execution Environment
# Run playbook in a container
ansible-runner run . \
-p ping.yml \
--inventory inventory/hosts \
--container-image=quay.io/ansible/ansible-runner:latest
# With a custom EE
ansible-runner run . \
-p site.yml \
--inventory inventory/hosts \
--container-image=my_ee:latest
Practical Example
# Create project structure
mkdir -p my-project/project my-project/inventory
# Create inventory
cat > my-project/inventory/hosts << 'EOF'
[local]
localhost ansible_connection=local
EOF
# Create playbook
cat > my-project/project/ping.yml << 'EOF'
---
- name: Ping test
hosts: all
gather_facts: false
tasks:
- name: Test connection
ansible.builtin.ping:
EOF
# Run it
ansible-runner run my-project -p ping.yml --inventory inventory/hosts
Output
PLAY [Ping test] ***************************
TASK [Test connection] *********************
ok: [localhost]
PLAY RECAP *********************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Building Custom Execution Environments
Use ansible-builder to create custom EEs:
Install ansible-builder
pip install ansible-builder
Create EE Definition
# execution-environment.yml
---
version: 3
dependencies:
galaxy: requirements.yml
python: requirements.txt
system: bindep.txt
images:
base_image:
name: quay.io/ansible/ansible-runner:latest
# requirements.yml
---
collections:
- name: amazon.aws
version: ">=7.0.0"
- name: community.general
version: ">=8.0.0"
- name: ansible.posix
# requirements.txt
boto3>=1.28.0
botocore>=1.31.0
pywinrm>=0.4.3
# bindep.txt
gcc [compile]
python3-devel [compile]
Build the EE
# Build with podman
ansible-builder build --tag my_ee:latest
# Build with docker
ansible-builder build --tag my_ee:latest --container-runtime docker
# Verify
podman images | grep my_ee
Run with Custom EE
ansible-runner run . \
-p site.yml \
--container-image=my_ee:latest
Configuration Files
env/settings (JSON)
{
"process_isolation": true,
"process_isolation_executable": "podman",
"container_image": "my_ee:latest",
"container_volume_mounts": [
"/home/deploy/.ssh:/home/runner/.ssh:Z"
]
}
env/envvars
{
"AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE",
"AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLE",
"ANSIBLE_FORCE_COLOR": "true"
}
env/extravars
{
"target_env": "production",
"app_version": "2.1.0"
}
env/cmdline
--tags deploy --limit webservers -v
Advanced Usage
Run with Volume Mounts
# Mount SSH keys into container
ansible-runner run . \
-p site.yml \
--container-image=my_ee:latest \
--container-volume-mount="/home/user/.ssh:/home/runner/.ssh:Z"
Run with Environment Variables
ansible-runner run . \
-p aws_deploy.yml \
--container-image=my_ee:latest \
--cmdline="--extra-vars 'target_env=staging'" \
--container-volume-mount="/home/user/.aws:/home/runner/.aws:Z"
Transmit Mode (Remote Execution)
# Package project for remote execution
ansible-runner transmit . --cmdline="-p site.yml"
# On remote host
ansible-runner worker < payload.zip
# Collect results
ansible-runner process results/
ansible-runner vs ansible-playbook
| Feature | ansible-playbook | ansible-runner |
|---|---|---|
| Container support | ❌ No | ✅ Built-in |
| Structured output | Text only | JSON artifacts |
| Artifact storage | None | Automatic |
| API integration | CLI only | Python API available |
| AAP/Tower compat | Indirect | Direct |
| Isolation | None | Container-based |
Troubleshooting
Container Not Found
# Error: container image not found
# Fix: pull the image first
podman pull quay.io/ansible/ansible-runner:latest
Permission Denied on Mounted Volumes
# Fix: use :Z flag for SELinux
--container-volume-mount="/path:/path:Z"
Python Package Missing in EE
# Check what's inside the EE
podman run --rm my_ee:latest pip list
# Rebuild with the missing package
# Add to requirements.txt, then:
ansible-builder build --tag my_ee:latest
Slow First Run
The first run downloads the container image. Subsequent runs use the cached image:
# Pre-pull to avoid delays
podman pull my_ee:latest
Best Practices
- Pin collection versions in
requirements.yml— avoid surprise updates - Use multi-stage builds — keep EE images small
- Tag images with versions —
my_ee:1.0.0, not justmy_ee:latest - Store EE definitions in Git — version your build configs
- Use podman over docker — rootless containers, no daemon
- Mount SSH keys read-only —
--container-volume-mount="/ssh:/ssh:ro,Z" - Cache pip packages — speed up builds with
--build-arg PIP_CACHE_DIR
Related Articles
- Ansible Execution Environments vs virtualenv vs Docker
- Ansible Builder Guide
- Ansible Automation Platform Guide
- Containerized Ansible Installation
Conclusion
ansible-runner is the standard way to run Ansible playbooks inside Execution Environments. It provides container isolation, structured JSON output, and direct integration with Ansible Automation Platform. Use ansible-builder to create custom EEs with your required collections and Python dependencies, then run them with ansible-runner run --container-image. This ensures consistent, reproducible automation across development, CI/CD, and production environments.