AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

AAP 2.6 Execution Environments: Build, Manage, and Deploy Custom EEs

By Luca Berton · Published 2024-01-01 · Category: installation

Complete guide to Execution Environments in AAP 2.6. Build custom EEs with ansible-builder, manage images in Private Automation Hub, troubleshoot dependency.

What Are Execution Environments?

Execution Environments (EEs) are container images that package everything needed to run Ansible automation in a consistent, portable, and isolated way: • Ansible Core runtime • Python packages required by collections and custom modules • System packages (libraries, binaries) • Ansible Collections pre-installed • Custom plugins and filter plugins

EEs replace the legacy approach of managing Python virtual environments and system dependencies directly on control nodes. In AAP 2.6, all automation runs inside EEs — on both Automation Controller and locally with ansible-navigator.

See also: Ansible Execution Environments: Build Custom EEs for Enterprise Automation

Why EEs Solve Real Problems

Before EEs (The Pain)

Control Node
├── Python 3.9 virtualenv (for network team)
│   ├── paramiko 2.x
│   ├── netmiko 4.x
│   └── cisco.ios collection
├── Python 3.11 virtualenv (for cloud team)
│   ├── boto3 1.x
│   ├── azure-mgmt-compute
│   └── amazon.aws collection
└── System packages: libxml2, libxslt, openssl
    └── Version conflicts between teams!

With EEs (The Solution)

ee-network:1.0          ee-cloud:1.0           ee-security:1.0
├── Python 3.11         ├── Python 3.11        ├── Python 3.11
├── paramiko 3.x        ├── boto3 1.x          ├── python-nmap
├── netmiko 4.x         ├── azure-mgmt-*       ├── openscap
├── cisco.ios           ├── amazon.aws         ├── ansible.posix
└── Isolated!           └── Isolated!          └── Isolated!

Each team gets their own container image with exactly the dependencies they need. No conflicts, no drift, completely reproducible.

Red Hat Provided EE Images

AAP 2.6 ships with base images from the Red Hat registry:

| Image | Contents | Use Case | |-------|----------|----------| | ee-minimal-rhel9 | Ansible Core + minimal Python deps | Lightweight tasks, custom base | | ee-supported-rhel9 | Core + Red Hat certified collections | General enterprise automation | | ee-29-rhel9 | Core 2.16 + latest supported collections | Latest features and modules |

Pull from the Red Hat registry:

podman login registry.redhat.io
podman pull registry.redhat.io/ansible-automation-platform-26/ee-supported-rhel9:latest

See also: Ansible Builder & Execution Environments: Complete Guide (2026)

Building Custom EEs with ansible-builder

Install ansible-builder

pip install ansible-builder

Execution Environment Definition (v3)

Create execution-environment.yml:

---
version: 3

images: base_image: name: registry.redhat.io/ansible-automation-platform-26/ee-minimal-rhel9:latest

dependencies: galaxy: collections: - name: cisco.ios version: ">=9.0.0" - name: cisco.nxos version: ">=9.0.0" - name: ansible.netcommon version: ">=7.0.0" - name: ansible.utils version: ">=5.0.0" python: - paramiko>=3.0 - netmiko>=4.0 - textfsm>=1.1 - ntc-templates>=6.0 - jmespath>=1.0 system: - openssh-clients [platform:rpm] - iputils [platform:rpm] - bind-utils [platform:rpm]

additional_build_steps: prepend_base: - RUN whoami - RUN cat /etc/os-release append_final: - RUN pip3 freeze | grep -i ansible - RUN ansible-galaxy collection list

Build the Image

# Build with Podman (default)
ansible-builder build \
  --tag registry.example.com/ee-network:1.0 \
  --container-runtime podman \
  --prune-images

# Build with Docker ansible-builder build \ --tag registry.example.com/ee-network:1.0 \ --container-runtime docker

Build Output

Running command:
  podman build -f context/Containerfile -t registry.example.com/ee-network:1.0 context
Step 1/14 : FROM registry.redhat.io/ansible-automation-platform-26/ee-minimal-rhel9:latest
Step 2/14 : RUN whoami
 ---> runner
Step 3/14 : RUN pip3 install paramiko>=3.0 netmiko>=4.0 textfsm>=1.1 ...
Step 4/14 : RUN ansible-galaxy collection install cisco.ios>=9.0.0 ...
...
Complete! The build context can be found at: context

EE Definition Deep Dive

Galaxy Dependencies (Collections)

Specify collections with version constraints:

dependencies:
  galaxy:
    collections:
      - name: amazon.aws
        version: ">=9.0.0,<10.0.0"
      - name: community.aws
        version: ">=9.0.0"
      - name: amazon.cloud
      # Git source for unreleased collections
      - name: custom.internal
        source: https://github.com/myorg/ansible-custom-collection
        type: git
        version: main

Or reference a requirements.yml file:

dependencies:
  galaxy: requirements.yml

Python Dependencies

Specify Python packages:

dependencies:
  python:
    - boto3>=1.34
    - botocore>=1.34
    - jmespath>=1.0
    - netaddr>=1.0

Or reference a requirements.txt file:

dependencies:
  python: requirements.txt

System Dependencies

Use Bindep format for system packages:

dependencies:
  system:
    - gcc [platform:rpm compile]
    - python3-devel [platform:rpm compile]
    - libxml2-devel [platform:rpm]
    - libxslt-devel [platform:rpm]
    - openssl-devel [platform:rpm]
    - krb5-libs [platform:rpm]
    - krb5-devel [platform:rpm]

Additional Build Steps

Inject custom Containerfile instructions:

additional_build_steps:
  prepend_base:
    - ENV MY_VAR=production
    - RUN mkdir -p /opt/custom/plugins

prepend_galaxy: - COPY ansible.cfg /etc/ansible/ansible.cfg

prepend_builder: - RUN pip3 install --upgrade pip

prepend_final: - RUN mkdir -p /opt/app/data

append_final: - COPY custom_filter_plugins/ /usr/share/ansible/plugins/filter/ - COPY custom_modules/ /usr/share/ansible/plugins/modules/ - RUN ansible --version - LABEL maintainer="platform-team@example.com" - LABEL version="1.0"

See also: Ansible Automation Platform 2.6 Architecture and Components: Complete Guide

Publishing EEs to Private Automation Hub

Push your custom EE to Private Automation Hub for use across your organization:

# Tag the image for your Hub registry
podman tag registry.example.com/ee-network:1.0 \
  hub.example.com/ee-network:1.0

# Login to Private Automation Hub podman login hub.example.com

# Push the image podman push hub.example.com/ee-network:1.0

Configure Controller to Use Custom EE

- name: Register custom EE in Controller
  ansible.platform.execution_environment:
    controller_host: "{{ gateway_url }}"
    controller_username: "{{ controller_user }}"
    controller_password: "{{ controller_pass }}"
    name: "Network Automation EE"
    image: "hub.example.com/ee-network:1.0"
    pull: "missing"  # missing, always, or never
    description: "Custom EE for Cisco/Arista network automation"
    state: present

- name: Assign EE to job template ansible.platform.job_template: controller_host: "{{ gateway_url }}" controller_username: "{{ controller_user }}" controller_password: "{{ controller_pass }}" name: "Network Configuration Backup" execution_environment: "Network Automation EE" state: present

Testing EEs Locally with ansible-navigator

Before pushing to production, test your EE locally:

# Run a playbook inside the custom EE
ansible-navigator run playbook.yml \
  --eei registry.example.com/ee-network:1.0 \
  --mode stdout

# Interactive mode — explore the EE ansible-navigator --eei registry.example.com/ee-network:1.0

# List collections inside the EE ansible-navigator collections \ --eei registry.example.com/ee-network:1.0

# Check installed Python packages ansible-navigator exec \ --eei registry.example.com/ee-network:1.0 \ -- pip3 freeze

# Inspect EE metadata ansible-navigator images \ --eei registry.example.com/ee-network:1.0

Multi-Stage Builds for Smaller Images

Optimize EE image size by separating build-time and runtime dependencies:

---
version: 3

images: base_image: name: registry.redhat.io/ansible-automation-platform-26/ee-minimal-rhel9:latest

dependencies: galaxy: collections: - name: community.crypto - name: ansible.posix python: - cryptography>=42.0 - pyOpenSSL>=24.0 system: # Build-time only — install headers for compilation - gcc [platform:rpm compile] - python3-devel [platform:rpm compile] - openssl-devel [platform:rpm compile] # Runtime only — needed at execution time - openssl [platform:rpm]

additional_build_steps: append_final: # Clean up build artifacts - RUN pip3 cache purge - RUN dnf clean all - RUN rm -rf /var/cache/dnf /tmp/*

The [platform:rpm compile] tag tells ansible-builder to install these packages only during the build stage and remove them from the final image.

Versioning and CI/CD for EEs

Automated EE Build Pipeline

# .github/workflows/build-ee.yml
name: Build Execution Environment
on:
  push:
    paths:
      - 'execution-environments/**'
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Weekly Monday 6 AM

jobs: build: runs-on: ubuntu-latest strategy: matrix: ee: [ee-network, ee-cloud, ee-security] steps: - uses: actions/checkout@v4

- name: Install ansible-builder run: pip install ansible-builder

- name: Build EE run: | cd execution-environments/${{ matrix.ee }} ansible-builder build \ --tag hub.example.com/${{ matrix.ee }}:${{ github.sha }} \ --tag hub.example.com/${{ matrix.ee }}:latest

- name: Push to Hub run: | podman login hub.example.com -u ${{ secrets.HUB_USER }} -p ${{ secrets.HUB_PASS }} podman push hub.example.com/${{ matrix.ee }}:${{ github.sha }} podman push hub.example.com/${{ matrix.ee }}:latest

Semantic Versioning for EEs

# Tag with semantic versions
podman tag ee-network:latest hub.example.com/ee-network:2.1.0
podman tag ee-network:latest hub.example.com/ee-network:2.1
podman tag ee-network:latest hub.example.com/ee-network:2

# Push all tags podman push hub.example.com/ee-network:2.1.0 podman push hub.example.com/ee-network:2.1 podman push hub.example.com/ee-network:2

Troubleshooting

Collection Dependency Conflicts

ERROR: Cannot install ansible.netcommon>=7.0.0 and ansible.utils>=4.0.0
because these package versions have conflicting dependencies.

Fix: Pin compatible versions explicitly:

dependencies:
  galaxy:
    collections:
      - name: ansible.netcommon
        version: "7.1.0"
      - name: ansible.utils
        version: "5.0.0"

Python Package Build Failures

error: command 'gcc' not found

Fix: Add build-time system dependencies:

dependencies:
  system:
    - gcc [platform:rpm compile]
    - python3-devel [platform:rpm compile]

Image Too Large

# Check image size
podman images | grep ee-network
# ee-network  1.0  abc123  1.2 GB

# Reduce size: # 1. Use ee-minimal instead of ee-supported as base # 2. Add cleanup steps in additional_build_steps # 3. Remove unnecessary collections # 4. Use multi-stage builds with compile tags

Authentication to Red Hat Registry

Error: unauthorized: access to the requested resource is not authorized

Fix: Login to the Red Hat registry with your RHN credentials:

podman login registry.redhat.io
# Username: your-rhn-username
# Password: your-rhn-password

FAQ

Can I use community base images instead of Red Hat?

Yes, but only Red Hat base images are supported under your AAP subscription. Community images like quay.io/ansible/ansible-runner work for development but are not tested or supported by Red Hat for production AAP deployments.

How often should I rebuild EEs?

Rebuild when collection versions change, Python dependencies update, or security patches are released. A weekly automated build via CI/CD is a good baseline. Always rebuild after Ansible Core security advisories.

Can I run different EEs for different job templates?

Yes. Each job template in Automation Controller can specify its own Execution Environment. This is the recommended approach — build purpose-specific EEs (network, cloud, security, Windows) rather than one monolithic image.

What is the difference between ansible-builder v2 and v3?

Version 3 of the EE definition format (used in this guide) adds support for multi-stage builds via [compile] tags, multiple base image selection, and more flexible additional_build_steps hooks. Always use version: 3 for new EEs.

Can execution nodes pull EEs from external registries?

Execution nodes pull EE images from the registry configured in Automation Controller. For air-gapped environments, use Private Automation Hub as a local registry mirror. Execution nodes must have network access to the configured registry.

Conclusion

Execution Environments are foundational to AAP 2.6's architecture. They eliminate dependency conflicts, ensure reproducibility across environments, and enable teams to independently manage their automation toolchains. Invest in a proper EE build pipeline early — it pays dividends as your automation scales.

Related Articles

AAP 2.6 Architecture and Components: Complete GuideAAP 2.6 RPM Deprecation and Containerized MigrationAAP 2.6 New Collections and IntegrationsAAP 2.6 Configuration as Code with ansible.platformAAP 2.6 Security Best Practices

See also

Ansible Builder & Execution Environments: Complete Guide (2026)

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home