Ansible Execution Environments: Stop Fighting Dependencies, Ship Faster
Ansible Execution Environments solve dependency hell.
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
- ✓Docker or Podman installed. Basic familiarity with container concepts. Ansible 2.12+ installed.
Ansible Execution Environments are container images that package Ansible with all its dependencies. Build them with ansible-builder, run them with ansible-navigator. They solve dependency conflicts and ensure consistent automation execution across teams and CI/CD pipelines.
Imagine you're a chef who needs to cook the same dish in different kitchens. Without EEs, you'd have to check each kitchen for the right pans, spices, and stove settings. With EEs, you bring your own portable kitchen — a container with everything you need. You just plug it in and cook. No surprises.
Every team I've worked with has a horror story about Ansible breaking because someone's laptop had a different Python version or a collection conflicted with a system package. I've seen a pip install inside a playbook silently upgrade a library and take down a production deployment at 2 AM. The root cause? No isolation. Ansible Execution Environments are the fix — they containerize your automation stack so you never have to debug a 'but it works on my machine' again. By the end of this, you'll be able to build, test, and debug EEs in production, and you'll know exactly when they're overkill.
Why Your Ansible Setup Is Broken (And EEs Fix It)
Before EEs, every Ansible control node was a snowflake. You'd have a Jenkins server with Python 3.6, a developer's Mac with Python 3.9, and a production bastion with Python 3.8. Collections installed via ansible-galaxy would conflict with system packages. Someone would run pip install --upgrade and break everything. EEs solve this by bundling Ansible, Python, collections, and system dependencies into a single container image. Now every run — local, CI, production — uses the exact same environment. No more 'works on my machine'.
latest as your EE tag in CI. Pin to a specific version like my-ee:v1.2.3. I've seen latest get rebuilt mid-deployment and break a playbook because a collection API changed.Building Your First EE: The Right Way
You don't build EEs by hand — you use ansible-builder. It reads a definition file (execution-environment.yml) and generates a Containerfile (or Dockerfile) for you. The key is to keep it minimal. Start with a base image from quay.io/ansible/ansible-runner. Then add only the collections and Python packages your playbook needs. Use bindep.txt for system-level dependencies (like libffi-dev for cryptography). The build process is: ansible-builder build -f execution-environment.yml -t my-ee:1.0.0. This creates a container image you can push to any registry.
ansible-builder introspect to scan your playbook directory and auto-generate requirements.yml and requirements.txt. It's not perfect, but it's a great starting point.Running Playbooks with ansible-navigator
ansible-navigator is the CLI tool that runs your playbook inside the EE. It handles mounting inventory, playbooks, and SSH keys into the container. The basic command: ansible-navigator run playbook.yml --eei my-ee:1.0.0 --mode stdout. The --mode stdout flag makes it behave like traditional ansible-playbook — otherwise you get an interactive TUI. For CI, always use --mode stdout and --pull-policy missing to avoid pulling the image every time if it's already cached.
--env in the command line. Use --pass-environment-variable to forward existing env vars, or mount a secrets file. Your shell history is not a vault.Debugging EE Failures: The TUI and Logs
When a playbook fails inside an EE, you can't just SSH into the container — it's ephemeral. Use ansible-navigator's interactive mode (--mode interactive) to explore the EE after a failure. Or, for CI, capture the container logs by running the EE manually: podman run --rm -it my-ee:1.0.0 /bin/bash. Inside, you can check Python versions, collection paths, and test imports. Common failure: a collection's Python dependency is missing. Fix it by adding to requirements.txt and rebuilding.
requirements.txt and rebuild. Always version-pin your dependencies.CI/CD Integration: The Pipeline That Doesn't Lie
EEs shine in CI. Your pipeline builds the EE once, pushes it to a registry, then every job pulls the same image. No more 'but it passed on Jenkins'. Use a multi-stage build: first stage installs dependencies, second stage copies only what's needed. Tag with the commit SHA for traceability. In your pipeline, run ansible-navigator run with --pull-policy always to ensure you're using the exact image from the registry. Example GitLab CI job: image: $CI_REGISTRY/ansible-ee:$CI_COMMIT_SHA.
--pull-policy always in deploy jobs — it adds latency and can fail if the registry is down. Use --pull-policy missing or never after the image is verified in the test stage.When EEs Are Overkill (And What to Use Instead)
EEs add complexity. You need a container runtime, a registry, and the build pipeline. For a single playbook run weekly on a laptop, a virtual environment (python3 -m venv) with pip install ansible is simpler. For teams with fewer than 5 people and no CI, EEs are overkill. But if you have multiple projects with conflicting dependencies, or you're running automation in production, EEs are the only sane choice. The rule: if you've ever said 'but it works on my machine', you need EEs.
ansible-runner with a virtual environment instead of full EEs. It's lighter and doesn't require container skills. Migrate to EEs when you hit dependency conflicts or need to scale.The 4GB Container That Kept Dying
Error: OOMKilled after 10 minutes of execution.boto3, google-cloud-sdk, and azure-cli. The image was 4GB. Podman's default memory limit for containers is 2GB. The container hit the limit during a large inventory parse.--memory=4g in the CI runner config. Reduced collection set to only what each pipeline needed.- An EE is not a dump truck.
- Only include what your playbook actually needs.
- A lean EE is a fast, stable EE.
Error: Unable to pull image or manifest not foundpodman pull <image> manually to test. 4. If using --pull-policy always, ensure network access.ModuleNotFoundErrorpodman run --rm -it <ee-image> /bin/bash. 2. Try python3 -c "import <module>". 3. If missing, add to requirements.txt and rebuild. 4. Check if collection is installed: ansible-galaxy collection list.podman inspect <container> | jq '.[0].HostConfig.Memory'. 2. Increase memory via --container-options '--memory=4g'. 3. Reduce EE image size by trimming collections. 4. Add swap or use a larger instance.podman images | grep <ee-name>podman pull <registry>/<ee-name>:<tag>ansible-builder build -f execution-environment.yml -t <tag> && podman push <tag>| File | Command / Code | Purpose |
|---|---|---|
| execution-environment.yml | version: 1 | Why Your Ansible Setup Is Broken (And EEs Fix It) |
| requirements.yml | collections: | Building Your First EE |
| run_playbook.sh | ansible-navigator run site.yml \ | Running Playbooks with ansible-navigator |
| debug_ee.sh | podman run --rm -it \ | Debugging EE Failures |
| .gitlab-ci.yml | stages: | CI/CD Integration |
Key takeaways
Common mistakes to avoid
3 patternsBuilding one monolithic EE for everything
Using :latest tags in production
my-ee:1.0.0). The :latest tag is mutable and breaks reproducibility.Not pinning collection versions in the EE
requirements.yml within the EE definition. A collection upgrade in the EE can break playbooks just like a library upgrade.Interview Questions on This Topic
How does ansible-navigator handle SSH agent forwarding inside an EE? What happens if the agent socket is not mounted?
--ssh-forward to mount the SSH agent socket into the container. Without it, SSH connections from inside the EE will fail with Permission denied (publickey). The socket path is passed via SSH_AUTH_SOCK environment variable. If not mounted, you must either copy keys into the image (bad practice) or use password authentication (worse).Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
That's Ansible. Mark it forged?
3 min read · try the examples if you haven't