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.

Ansible 13 Upgrade Guide: Breaking Changes, Removals, and Migration Steps

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

Complete Ansible 13 upgrade guide covering all breaking changes in ansible-core 2.20. Python 3.12+ required, INJECT_FACTS_AS_VARS deprecated, failed_when.

Ansible 13 is based on ansible-core 2.20. The biggest changes: Python 3.12+ required on controllers, Python 3.9+ on targets, INJECT_FACTS_AS_VARS deprecated, and the failed_when exception key renamed. Here's everything you need to update before upgrading.

Prerequisites

Python Version Requirements

| Component | Ansible 12 (core 2.19) | Ansible 13 (core 2.20) | |-----------|----------------------|----------------------| | Controller | Python 3.11+ | Python 3.12+ | | Target hosts | Python 3.8+ | Python 3.9+ |

# Check your Python version
python3 --version

# If below 3.12, upgrade first # Ubuntu/Debian: sudo apt install python3.12

# RHEL/CentOS: sudo dnf install python3.12

This is the most common upgrade blocker — ensure all controller nodes run Python 3.12+.

Upgrade Path

Always upgrade through Ansible 12 first. The ansible-core 2.19 templating changes (introduced in Ansible 12) are prerequisites for Ansible 13.

# Step 1: Upgrade to Ansible 12 first
pip install 'ansible>=12,<13'

# Step 2: Fix any templating issues (see Ansible 12 Upgrade Guide)

# Step 3: Upgrade to Ansible 13 pip install 'ansible>=13,<14'

See also: Ansible 12 Upgrade Guide: Breaking Changes, Data Tagging & What to Test First

Breaking Changes in ansible-core 2.20

1. INJECT_FACTS_AS_VARS Deprecated

The biggest behavioral change. Currently, Ansible injects facts as top-level variables (ansible_distribution) AND stores them in the ansible_facts dictionary (ansible_facts['distribution']). The top-level injection is deprecated and will be removed in ansible-core 2.24.

# ❌ DEPRECATED — will stop working in 2.24
- ansible.builtin.debug:
    msg: "OS: {{ ansible_distribution }}"

- ansible.builtin.debug: msg: "IP: {{ ansible_default_ipv4.address }}"

# ✅ RECOMMENDED — works in all versions - ansible.builtin.debug: msg: "OS: {{ ansible_facts['distribution'] }}"

- ansible.builtin.debug: msg: "IP: {{ ansible_facts['default_ipv4']['address'] }}"

Important: Inside ansible_facts, the ansible_ prefix is removed:

| Old (deprecated) | New (recommended) | |-------------------|-------------------| | ansible_distribution | ansible_facts['distribution'] | | ansible_os_family | ansible_facts['os_family'] | | ansible_hostname | ansible_facts['hostname'] | | ansible_default_ipv4 | ansible_facts['default_ipv4'] | | ansible_memtotal_mb | ansible_facts['memtotal_mb'] | | ansible_processor_vcpus | ansible_facts['processor_vcpus'] |

To silence warnings now (while migrating):

# ansible.cfg
[defaults]
inject_facts_as_vars = True

Migration strategy: Search your playbooks for ansible_ variables and update them. This is the highest-effort change in Ansible 13.

# Find all affected files
grep -rn 'ansible_distribution\|ansible_os_family\|ansible_hostname\|ansible_default_ipv4\|ansible_memtotal' roles/ playbooks/

2. failed_when Exception Key Renamed

When you use failed_when: false to suppress errors, the exception key in the result has been renamed:

# ❌ BREAKS 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

# ✅ WORKS in 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

3. PowerShell Quote Stripping Removed

Windows module utilities no longer automatically remove quotes from paths:

# If you relied on automatic quote removal:
- ansible.windows.win_copy:
    src: '"C:\source\file.txt"'    # Quotes were previously stripped
    dest: C:\dest\

# Fix: Remove the extra quotes yourself - ansible.windows.win_copy: src: 'C:\source\file.txt' dest: C:\dest\

4. smart Transport Removed

The DEFAULT_TRANSPORT = smart option (which auto-selected ssh or paramiko) has been removed:

# ❌ BREAKS in Ansible 13
[defaults]
transport = smart

# ✅ FIX — specify explicitly [defaults] transport = ssh # or transport = paramiko

5. DataLoader.get_basedir Returns Absolute Path

If you write custom plugins that use DataLoader.get_basedir(), it now returns an absolute path instead of relative. Update any path manipulation code accordingly.

6. Argument Spec: None Treated as Empty String

None values are now treated as empty strings for the str type in argument spec validation. This improves consistency with pre-2.19 templating conversions but may affect custom modules that rely on None vs "" distinction.

Removed Features

These previously deprecated features are now gone:

| Removed | Replacement | |---------|-------------| | vault/unvault filter vaultid param | Use vault_id instead | | Galaxy v2 server API | Galaxy servers must support v3 | | dnf/dnf5 install_repoquery option | Remove from playbooks | | encrypt module passlib_or_crypt API | Use updated API | | paramiko PARAMIKO_HOST_KEY_AUTO_ADD | Use SSH config | | paramiko PARAMIKO_LOOK_FOR_KEYS | Use SSH config | | yum_repository keepcache option | Remove from playbooks | | Vars plugins get_host_vars/get_group_vars fallback | Inherit from BaseVarsPlugin |

See also: AAP 2.6 Migration from AWX: Complete Upgrade and Data Migration Guide

Collection-Level Breaking Changes

community.mysql

# Python 2 no longer supported on targets
# mysqlclient connector deprecated — use PyMySQL

# mysql_db: pipefail now defaults to true - community.mysql.mysql_db: name: mydb state: dump target: /tmp/dump.sql pipefail: false # Set explicitly if not using bash

community.vmware

# Requires ansible-core 2.19+
# Requires vmware.vmware >= 2.0.0
# Dependencies changed: pyvmomi → vcf-sdk
pip install vcf-sdk    # New dependency

# Removed modules (use vmware.vmware collection): # vmware_cluster → vmware.vmware.cluster # vmware_cluster_dpm → vmware.vmware.cluster_dpm # vmware_cluster_drs → vmware.vmware.cluster_drs

community.general

# Removed modules:
# - bearychat (service discontinued)
# - facter (replaced by community.general.facter_facts)
# - yaml callback (use default callback with result_format=yaml)

# Deprecated — will be removed in community.general 13.0.0: # - catapult, dimensiondata_*, typetalk, hiera lookup # - spotinst_aws_elastigroup, pushbullet

community.docker

# Docker SDK for Python 1.x (docker-py) no longer supported
pip install 'docker>=2.0.0'    # Required

# Python 3.6 and below no longer supported # ansible-core 2.15/2.16 no longer supported

awx.awx

The awx.awx collection will be removed from Ansible 14 due to ongoing refactoring. Plan your migration:

# After removal, install manually:
ansible-galaxy collection install awx.awx

include_vars Changes

Two breaking changes for include_vars:

# ❌ BREAKS — extensions must be a list
- ansible.builtin.include_vars:
    dir: vars/
    extensions: "yml"    # String no longer accepted

# ✅ FIX - ansible.builtin.include_vars: dir: vars/ extensions: ["yml"]

# ❌ DEPRECATED — ignore_files as string - ansible.builtin.include_vars: dir: vars/ ignore_files: ".gitkeep"

# ✅ FIX - ansible.builtin.include_vars: dir: vars/ ignore_files: [".gitkeep"]

See also: How to Upgrade from AAP 2.4 to AAP 2.6 — Step-by-Step Guide

replace Module: Unicode Mode

The replace module now reads files as unicode instead of bytes:

# If you relied on byte-level regex matching,
# this may produce different results
- ansible.builtin.replace:
    path: /etc/config
    regexp: '\xc3\xa9'    # Byte pattern — may not work
    replace: 'é'

# Fix: Use unicode patterns directly - ansible.builtin.replace: path: /etc/config regexp: 'é' replace: 'e'

Pre-Upgrade Checklist

1. [ ] Python 3.12+ on all controller nodes
2. [ ] Python 3.9+ on all target hosts
3. [ ] Upgraded to Ansible 12 first (fix templating issues)
4. [ ] Searched playbooks for `ansible_` fact variables → migrate to `ansible_facts['...']`
5. [ ] Searched for `result.exception` → rename to `result.failed_when_suppressed_exception`
6. [ ] Removed `transport = smart` from ansible.cfg
7. [ ] Updated include_vars: extensions/ignore_files to lists
8. [ ] Checked vault/unvault filters for deprecated `vaultid` parameter
9. [ ] Updated community.vmware: install vcf-sdk, update module names
10. [ ] Updated community.docker: ensure docker SDK >= 2.0.0
11. [ ] Tested in staging with Ansible 13

FAQ

Can I skip Ansible 12 and go straight to 13?

Not recommended. Ansible 12 (core 2.19) introduced major templating changes that surface issues in playbooks. Upgrading through 12 first lets you fix those issues before adding the Ansible 13 changes on top.

How long until INJECT_FACTS_AS_VARS is fully removed?

The deprecation targets ansible-core 2.24 (approximately Ansible 17). You have several major versions to migrate, but starting now avoids a painful bulk migration later.

Will my roles from Ansible Galaxy still work?

Most will, but roles that use deprecated fact syntax (ansible_distribution vs ansible_facts['distribution']) will show deprecation warnings. Roles that use removed features (vault vaultid, smart transport) will break.

What about Execution Environments?

Update your EE container images to include Python 3.12+ and the latest collection versions. If your EE uses community.vmware, switch from pyvmomi to vcf-sdk.

Conclusion

Ansible 13's biggest changes are Python 3.12+ requirement and INJECT_FACTS_AS_VARS deprecation. Start migrating fact variable syntax now — it's the highest-effort change. Fix failed_when exception keys, remove smart transport, and update collection dependencies. Always upgrade through Ansible 12 first.

Related Articles

Ansible 12 Upgrade Guideansible-core 2.19 Templating ChangesAnsible facts: Gather, Use, CreateAnsible Variable Precedence Guide

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home