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

ComponentAnsible 12 (core 2.19)Ansible 13 (core 2.20)
Controller (minimum)Python 3.11Python 3.12
Controller (added)—Python 3.14
Target (minimum)Python 3.8Python 3.9
Target (added)—Python 3.14
Dropped controllerPython 3.10Python 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_TRANSPORT no longer supports smart value
  • vault/unvault filters: vaultid parameter removed — use vault_id instead
  • ansible-galaxy: v2 Galaxy server API removed — servers must support v3
  • dnf/dnf5: install_repoquery option removed
  • encrypt module utility: passlib_or_crypt API removed
  • paramiko: PARAMIKO_HOST_KEY_AUTO_ADD and PARAMIKO_LOOK_FOR_KEYS config keys removed
  • yum_repository: keepcache option removed
  • Vars plugins without BaseVarsPlugin inheritance: get_host_vars/get_group_vars fallback removed

Behavioral Changes

  • DataLoader.get_basedir now returns an absolute path (was relative)
  • Argument spec validation treats None as empty string for str type
  • failed_when suppressed exceptions: result.exception renamed to result.failed_when_suppressed_exception
  • replace module now uses text-mode unicode (not bytes) for file operations
  • include_vars: extensions and ignore_files parameters must be lists (strings rejected)

Deprecation Warnings (Prepare for 2.24)

  • INJECT_FACTS_AS_VARS defaults to True but will switch to False in 2.24 — update playbooks to use ansible_facts['distribution'] instead of ansible_os_distribution
  • include_vars: string value for ignore_files is deprecated — use a list
  • Internal vars cache 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-sdk
  • community.general: removes bearychat, yaml callback, facter module
  • Removed collections: community.digitalocean, ibm.qradar
  • awx.awx deprecated — 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 Packageansible-corePython ControllerPython TargetRelease
13.x2.203.12-3.143.9-3.14Nov 2025
12.x2.193.11-3.133.8-3.13Jul 2025
11.x2.183.11-3.133.8-3.13Nov 2024
10.x2.173.10-3.123.7-3.12May 2024
9.x2.163.10-3.123.7-3.12Nov 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

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.