The Problem
You're running Ansible 2.9+ and seeing deprecation warnings:
[DEPRECATION WARNING]: Using 'with_items' is deprecated. Use 'loop' with a list value instead.
This feature will be removed in version 2.14. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.Your playbook still works, but you want to fix it before the deprecated syntax is removed entirely.
Why This Happens
Ansible 2.5 introduced the loop keyword as a unified replacement for:
- with_items
- with_list
- with_dict
- with_subelements
- with_nested
- with_sequence
- with_together
The old syntax still works but is deprecated and will be removed.
Understanding the Difference
Old with_items syntax:
- name: Old style
debug:
msg: "{{ item }}"
with_items:
- item1
- item2New loop syntax:
- name: New style
debug:
msg: "{{ item }}"
loop:
- item1
- item2Migrating from with_items
Basic with_items Migration
Simple list migration:
```yaml # BEFORE - name: Install packages yum: name: "{{ item }}" state: present with_items: - nginx - mysql - redis
# AFTER - name: Install packages yum: name: "{{ item }}" state: present loop: - nginx - mysql redis ```
Variable-based list:
```yaml # BEFORE - name: Process items debug: msg: "{{ item }}" with_items: "{{ my_list }}"
# AFTER - name: Process items debug: msg: "{{ item }}" loop: "{{ my_list }}" ```
with_items with Flattening
with_items automatically flattens nested lists. loop does not:
```yaml # BEFORE - with_items flattens - name: Process debug: msg: "{{ item }}" with_items: - [a, b] - [c, d] # Results: a, b, c, d
# AFTER - must use flatten filter - name: Process debug: msg: "{{ item }}" loop: "{{ [[a, b], [c, d]] | flatten }}" ```
Or use explicit flattened list:
- name: Process
debug:
msg: "{{ item }}"
loop:
- a
- b
- c
- dwith_dict Migration
Dictionary iteration requires filter:
```yaml # BEFORE - name: Process dict debug: msg: "Key: {{ item.key }} Value: {{ item.value }}" with_dict: user1: alice user2: bob
# AFTER - use dict2items - name: Process dict debug: msg: "Key: {{ item.key }} Value: {{ item.value }}" loop: "{{ {'user1': 'alice', 'user2': 'bob'} | dict2items }}" ```
with_subelements Migration
Nested structure iteration:
```yaml # BEFORE - name: Process subelements debug: msg: "User: {{ item.0.name }} Group: {{ item.1 }}" with_subelements: - users - groups
# AFTER - use subelements filter - name: Process subelements debug: msg: "User: {{ item.0.name }} Group: {{ item.1 }}" loop: "{{ users | subelements('groups') }}" ```
with_nested Migration
Cross-product iteration:
```yaml # BEFORE - name: Process combinations debug: msg: "{{ item[0] }} - {{ item[1] }}" with_nested: - [a, b] - [1, 2]
# AFTER - use product filter - name: Process combinations debug: msg: "{{ item[0] }} - {{ item[1] }}" loop: "{{ ['a', 'b'] | product([1, 2]) | list }}" ```
with_sequence Migration
Number sequences:
```yaml # BEFORE - name: Create numbered items debug: msg: "Item {{ item }}" with_sequence: start=1 end=5
# AFTER - use range - name: Create numbered items debug: msg: "Item {{ item }}" loop: "{{ range(1, 6) | list }}" ```
with_together Migration
Parallel iteration:
```yaml # BEFORE - name: Process together debug: msg: "{{ item[0] }} - {{ item[1] }}" with_together: - [a, b, c] - [1, 2, 3]
# AFTER - use zip filter - name: Process together debug: msg: "{{ item[0] }} - {{ item[1] }}" loop: "{{ ['a', 'b', 'c'] | zip([1, 2, 3]) | list }}" ```
Common Migration Pitfalls
Pitfall 1: Flattening Differences
with_items flattens, loop does not:
```yaml # This behaves differently vars: nested_list: - [a, b] - [c]
# BEFORE - outputs a, b, c with_items: "{{ nested_list }}"
# AFTER - outputs [a,b], [c] (wrong!) loop: "{{ nested_list }}"
# CORRECT loop: "{{ nested_list | flatten }}" ```
Pitfall 2: Empty Lists
Both handle empty lists gracefully, but verify:
- name: Handle empty
debug:
msg: "{{ item }}"
loop: "{{ my_list | default([]) }}"
when: my_list | default([]) | length > 0Pitfall 3: Dictionary Key Access
Old dict syntax vs new:
```yaml # BEFORE with_dict: "{{ my_dict }}" # item.key and item.value available
# AFTER loop: "{{ my_dict | dict2items }}" # item.key and item.value available (same) ```
Verifying the Migration
Test converted playbooks:
```bash # Check syntax ansible-playbook playbook.yml --syntax-check
# Run in check mode ansible-playbook playbook.yml --check -v
# Run with deprecation warnings enabled ansible-playbook playbook.yml | grep -i deprecation ```
Suppressing Warnings Temporarily
While migrating, suppress warnings:
# ansible.cfg - temporary during migration
[defaults]
deprecation_warnings = FalseBut migrate properly before Ansible 2.14+.
Prevention
Write new playbooks using loop:
# New playbook template
- hosts: all
tasks:
- name: Modern loop
debug:
msg: "{{ item }}"
loop: "{{ items }}"
loop_control:
label: "{{ item.name | default(item) }}"Add ansible-lint to catch deprecated syntax:
ansible-lint playbook.yml
# Warns about with_items usage