Introduction
Ansible 13 ships with ansible-core 2.20, released November 2025. This major release drops support for older Python versions, removes long-deprecated features, and introduces new capabilities including play argument specs (tech preview) and third-party fact injection plugins. This guide walks you through a safe upgrade from Ansible 12 (ansible-core 2.19) to Ansible 13.
What Changed in Ansible 13
Python Version Changes
| Component | Ansible 12 (core 2.19) | Ansible 13 (core 2.20) |
|---|---|---|
| Controller (minimum) | Python 3.11 | Python 3.12 |
| Controller (added) | — | Python 3.14 |
| Target (minimum) | Python 3.8 | Python 3.9 |
| Target (added) | — | Python 3.14 |
| Dropped controller | Python 3.10 | Python 3.11 |
| Dropped target | — | Python 3.8 |
This is the most impactful change. If your Ansible controller runs Python 3.11, you must upgrade to 3.12+ before installing Ansible 13.
Key New Features
- Play argument specs (tech preview) — validate play-level variables with type checking
- Third-party fact injection plugins — extend fact gathering with custom plugins
- Register projections — selectively register parts of task results
- Updated ansible-test container images and VMs
- Python 3.14 support for both controller and target
Removed Deprecated Features
DEFAULT_TRANSPORTno longer supportssmartvaluevault/unvaultfilters:vaultidparameter removed — usevault_idinsteadansible-galaxy: v2 Galaxy server API removed — servers must support v3dnf/dnf5:install_repoqueryoption removedencryptmodule utility:passlib_or_cryptAPI removedparamiko:PARAMIKO_HOST_KEY_AUTO_ADDandPARAMIKO_LOOK_FOR_KEYSconfig keys removedyum_repository:keepcacheoption removed- Vars plugins without
BaseVarsPlugininheritance:get_host_vars/get_group_varsfallback removed
Behavioral Changes
DataLoader.get_basedirnow returns an absolute path (was relative)- Argument spec validation treats
Noneas empty string forstrtype failed_whensuppressed exceptions:result.exceptionrenamed toresult.failed_when_suppressed_exceptionreplacemodule now uses text-mode unicode (not bytes) for file operationsinclude_vars:extensionsandignore_filesparameters must be lists (strings rejected)
Deprecation Warnings (Prepare for 2.24)
INJECT_FACTS_AS_VARSdefaults toTruebut will switch toFalsein 2.24 — update playbooks to useansible_facts['distribution']instead ofansible_os_distributioninclude_vars: string value forignore_filesis deprecated — use a list- Internal
varscache will be removed in 2.24
Collection Changes
community.docker: requires Docker SDK 2.0.0+ (docker-py 1.x removed)community.mysql: drops Python 2, requires PyMySQL (mysqlclient deprecated)community.vmware: requires vmware.vmware >= 2.0.0, replaces pyvmomi with vcf-sdkcommunity.general: removesbearychat,yamlcallback,factermodule- Removed collections:
community.digitalocean,ibm.qradar awx.awxdeprecated — will be removed from Ansible 14
Pre-Upgrade Checklist
1. Check Python Version
# On the Ansible controller
python3 --version
# Must be 3.12+ for Ansible 13
# Check all target hosts
ansible all -m setup -a 'filter=ansible_python_version'
# Must be 3.9+ for Ansible 13 targets
2. Check Current Ansible Version
ansible --version
# ansible [core 2.19.x]
pip show ansible
# Version: 12.x.x
pip show ansible-core
# Version: 2.19.x
3. Audit Deprecated Feature Usage
# Run playbooks with deprecation warnings enabled
ANSIBLE_DEPRECATION_WARNINGS=True ansible-playbook site.yml --check
# Check for deprecated modules
grep -r "ansible.builtin\." playbooks/ roles/ | sort | uniq -c | sort -rn
4. Test Collection Compatibility
# List installed collections
ansible-galaxy collection list
# Check collection versions against Ansible 13 requirements
ansible-galaxy collection verify --ignore-errors
Upgrade Steps
Step 1: Upgrade Python (If Needed)
# Ubuntu/Debian
sudo apt update
sudo apt install python3.12 python3.12-venv python3.12-dev
# RHEL/Rocky/Alma
sudo dnf install python3.12 python3.12-devel
# macOS
brew install python@3.12
# Verify
python3.12 --version
Step 2: Create a Virtual Environment
# Always use a venv for Ansible
python3.12 -m venv ~/ansible-13-venv
source ~/ansible-13-venv/bin/activate
# Verify Python version in venv
python --version
# Python 3.12.x
Step 3: Install Ansible 13
# Fresh install
pip install ansible==13.*
# Or upgrade from Ansible 12
pip install --upgrade ansible
# Verify
ansible --version
# ansible [core 2.20.x]
Step 4: Update Collections
# Update all collections
ansible-galaxy collection install --upgrade -r requirements.yml
# Or update individually
ansible-galaxy collection install --upgrade community.general
ansible-galaxy collection install --upgrade ansible.posix
ansible-galaxy collection install --upgrade community.docker
Step 5: Run Tests
# Syntax check
ansible-playbook site.yml --syntax-check
# Check mode (dry run)
ansible-playbook site.yml --check --diff
# Run ansible-lint
pip install --upgrade ansible-lint
ansible-lint playbooks/ roles/
# Run on staging first
ansible-playbook site.yml --limit staging
Automate the Upgrade with Ansible
---
- name: Upgrade Ansible controller to v13
hosts: ansible_controllers
become: true
vars:
python_version: "3.12"
ansible_venv: /opt/ansible-13
tasks:
- name: Install Python 3.12
ansible.builtin.package:
name:
- "python{{ python_version }}"
- "python{{ python_version }}-venv"
- "python{{ python_version }}-dev"
state: present
- name: Create Ansible 13 virtual environment
ansible.builtin.pip:
name:
- ansible==13.*
- ansible-lint
virtualenv: "{{ ansible_venv }}"
virtualenv_command: "python{{ python_version }} -m venv"
- name: Verify installation
ansible.builtin.command: "{{ ansible_venv }}/bin/ansible --version"
register: ansible_ver
changed_when: false
- name: Display version
ansible.builtin.debug:
msg: "{{ ansible_ver.stdout_lines[0] }}"
- name: Update symlink
ansible.builtin.file:
src: "{{ ansible_venv }}/bin/ansible"
dest: /usr/local/bin/ansible
state: link
- name: Update ansible-playbook symlink
ansible.builtin.file:
src: "{{ ansible_venv }}/bin/ansible-playbook"
dest: /usr/local/bin/ansible-playbook
state: link
Upgrade Target Hosts Python
- name: Ensure targets have Python 3.9+
hosts: all
become: true
tasks:
- name: Check Python version
ansible.builtin.command: python3 --version
register: py_ver
changed_when: false
- name: Extract version number
ansible.builtin.set_fact:
python_minor: "{{ py_ver.stdout.split()[1].split('.')[1] | int }}"
- name: Install Python 3.12 on old targets
ansible.builtin.package:
name: python3.12
state: present
when: python_minor | int < 9
- name: Update ansible_python_interpreter
ansible.builtin.debug:
msg: >
Update inventory: set ansible_python_interpreter=/usr/bin/python3.12
for {{ inventory_hostname }}
when: python_minor | int < 9
Handle Breaking Changes
failed_when Exception Key Renamed
# ❌ Broken in Ansible 13
- ansible.builtin.command: /bin/false
register: result
failed_when: false
- ansible.builtin.debug:
msg: "Exception: {{ result.exception }}"
when: result.exception is defined
# ✅ Fixed for Ansible 13
- ansible.builtin.command: /bin/false
register: result
failed_when: false
- ansible.builtin.debug:
msg: "Exception: {{ result.failed_when_suppressed_exception }}"
when: result.failed_when_suppressed_exception is defined
Prepare for INJECT_FACTS_AS_VARS Deprecation
# ❌ Deprecated — will stop working in ansible-core 2.24
- ansible.builtin.debug:
msg: "OS: {{ ansible_distribution }}"
# ✅ Future-proof — works in all versions
- ansible.builtin.debug:
msg: "OS: {{ ansible_facts['distribution'] }}"
# Note: 'ansible_' prefix is removed inside ansible_facts
include_vars Must Use Lists
# ❌ Deprecated string value
- ansible.builtin.include_vars:
dir: vars/
ignore_files: ".gitkeep"
# ✅ Fixed — use a list
- ansible.builtin.include_vars:
dir: vars/
ignore_files: [".gitkeep"]
Python 3.8 Target Workaround
If you have legacy hosts stuck on Python 3.8:
# inventory — pin old targets to raw connection
[legacy_python38]
old-server-01 ansible_python_interpreter=/usr/bin/python3 ansible_connection=ssh
[legacy_python38:vars]
# Consider upgrading Python on these hosts
# Or use ansible.builtin.raw for basic tasks
# Upgrade Python on legacy hosts first
- name: Bootstrap Python 3.12 on legacy hosts
hosts: legacy_python38
gather_facts: false
tasks:
- name: Install Python 3.12 via raw
ansible.builtin.raw: |
apt-get update && apt-get install -y python3.12
changed_when: true
Collection Compatibility
Some older collection versions may not work with ansible-core 2.20:
# requirements.yml — pin compatible versions
collections:
- name: community.general
version: ">=10.0.0"
- name: community.docker
version: ">=4.0.0"
- name: ansible.posix
version: ">=2.0.0"
- name: community.crypto
version: ">=3.0.0"
- name: ansible.windows
version: ">=3.0.0"
Rollback Plan
# If something breaks, rollback to Ansible 12
pip install ansible==12.*
# Or keep both in separate venvs
source ~/ansible-12-venv/bin/activate # Old
source ~/ansible-13-venv/bin/activate # New
Version Mapping Reference
| Ansible Package | ansible-core | Python Controller | Python Target | Release |
|---|---|---|---|---|
| 13.x | 2.20 | 3.12-3.14 | 3.9-3.14 | Nov 2025 |
| 12.x | 2.19 | 3.11-3.13 | 3.8-3.13 | Jul 2025 |
| 11.x | 2.18 | 3.11-3.13 | 3.8-3.13 | Nov 2024 |
| 10.x | 2.17 | 3.10-3.12 | 3.7-3.12 | May 2024 |
| 9.x | 2.16 | 3.10-3.12 | 3.7-3.12 | Nov 2023 |
Upgrade Timeline
Week 1: Audit + prepare
├── Check Python versions across fleet
├── Run playbooks with deprecation warnings
├── Test collection compatibility
└── Update requirements.yml
Week 2: Upgrade controller
├── Install Python 3.12+ on controller
├── Create Ansible 13 venv
├── Install + verify
└── Run syntax checks + lint
Week 3: Test on staging
├── Run full playbook suite on staging
├── Fix any breaking changes
├── Update deprecated modules/parameters
└── Verify all roles work
Week 4: Production rollout
├── Upgrade target hosts Python if needed
├── Deploy to production
├── Monitor for issues
└── Update CI/CD pipelines
Related Articles
Conclusion
Upgrading to Ansible 13 (ansible-core 2.20) is primarily a Python version story: your controller needs Python 3.12+, and targets need Python 3.9+. The upgrade path: audit your Python versions and deprecated feature usage first, create a virtual environment with Python 3.12+, install Ansible 13, update collections, test on staging, then roll out. Keep your Ansible 12 venv as a rollback option. The new features — play argument specs and fact injection plugins — are worth the upgrade, but the Python modernization is the real driver.