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 combine Filter: Merge Dictionaries & Override Defaults (Guide)

By Luca Berton · Published 2024-01-01 · Category: database-automation

How to merge dictionaries in Ansible with the combine filter. Overlay defaults with overrides, recursive merge, handle nested dicts.

The combine filter merges two or more dictionaries in Ansible. Later dictionaries override earlier ones — making it perfect for default/override patterns.

Basic Usage

- name: Merge two dictionaries
  ansible.builtin.debug:
    msg: "{{ defaults | combine(overrides) }}"
  vars:
    defaults:
      port: 8080
      debug: false
      log_level: info
    overrides:
      debug: true
      log_level: debug
# Result: {"port": 8080, "debug": true, "log_level": "debug"}

The second dict (overrides) wins for overlapping keys.

See also: Ansible dict2items Filter: Convert Dictionaries to Lists (Guide)

Override Order

- name: Last dict wins
  ansible.builtin.debug:
    msg: "{{ dict1 | combine(dict2, dict3) }}"
  vars:
    dict1:
      a: 1
      b: 2
    dict2:
      b: 20
      c: 30
    dict3:
      c: 300
      d: 400
# Result: {"a": 1, "b": 20, "c": 300, "d": 400}

Recursive Merge (Nested Dicts)

By default, combine replaces nested dicts entirely. Use recursive=true to merge them:

- name: Shallow merge (default) — nested dict REPLACED
  ansible.builtin.debug:
    msg: "{{ base | combine(override) }}"
  vars:
    base:
      database:
        host: localhost
        port: 5432
        name: myapp
    override:
      database:
        host: db.prod.example.com
# Result: {"database": {"host": "db.prod.example.com"}}
# port and name are LOST!

- name: Recursive merge — nested dict MERGED ansible.builtin.debug: msg: "{{ base | combine(override, recursive=true) }}" vars: base: database: host: localhost port: 5432 name: myapp override: database: host: db.prod.example.com # Result: {"database": {"host": "db.prod.example.com", "port": 5432, "name": "myapp"}}

See also: Ansible regex_search & regex_replace Filters: Pattern Matching Guide

Common Patterns

Defaults + Environment Overrides

# group_vars/all.yml
app_defaults:
  workers: 4
  timeout: 30
  debug: false
  cache_ttl: 300

# group_vars/production.yml app_overrides: workers: 16 timeout: 60

# In playbook - name: Build final config ansible.builtin.set_fact: app_config: "{{ app_defaults | combine(app_overrides, recursive=true) }}" # Result: {"workers": 16, "timeout": 60, "debug": false, "cache_ttl": 300}

Merge Host-Specific Settings

- name: Build per-host config
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/myapp/app.conf
  vars:
    config: >-
      {{ global_config
         | combine(group_config | default({}), recursive=true)
         | combine(host_config | default({}), recursive=true) }}

Dynamic Key Addition

- name: Add a key to existing dict
  ansible.builtin.set_fact:
    my_dict: "{{ my_dict | combine({'new_key': 'new_value'}) }}"

Build Dict in a Loop

- name: Build dict from loop
  ansible.builtin.set_fact:
    user_map: "{{ user_map | default({}) | combine({item.name: item.uid}) }}"
  loop:
    - { name: alice, uid: 1001 }
    - { name: bob, uid: 1002 }
    - { name: charlie, uid: 1003 }
# Result: {"alice": 1001, "bob": 1002, "charlie": 1003}

Merge List of Dicts

- name: Merge a list of dicts into one
  ansible.builtin.debug:
    msg: "{{ dict_list | ansible.builtin.reduce('combine') }}"
  vars:
    dict_list:
      - { a: 1 }
      - { b: 2 }
      - { c: 3 }
# Result: {"a": 1, "b": 2, "c": 3}

combine vs union

# combine — for DICTIONARIES
- debug:
    msg: "{{ {'a': 1} | combine({'b': 2}) }}"
# Result: {"a": 1, "b": 2}

# union — for LISTS - debug: msg: "{{ [1, 2, 3] | union([3, 4, 5]) }}" # Result: [1, 2, 3, 4, 5]

List Handling

combine does NOT merge lists — the second dict's list replaces the first:

- name: Lists are replaced, not merged
  ansible.builtin.debug:
    msg: "{{ base | combine(override, recursive=true) }}"
  vars:
    base:
      packages:
        - nginx
        - python3
    override:
      packages:
        - nodejs
# Result: {"packages": ["nodejs"]}
# nginx and python3 are LOST

To merge lists inside dicts, use list_merge (Ansible 2.10+):

- name: Merge lists with list_merge
  ansible.builtin.debug:
    msg: "{{ base | combine(override, recursive=true, list_merge='append') }}"
  vars:
    base:
      packages: [nginx, python3]
    override:
      packages: [nodejs]
# Result: {"packages": ["nginx", "python3", "nodejs"]}

list_merge options: replace (default), keep, append, prepend, append_rp, prepend_rp.

See also: Ansible ternary Filter: Inline If-Else Conditional in Jinja2 (Guide)

FAQ

What's the difference between combine and union?

combine merges dictionaries (key-value pairs). union merges lists (deduplicating elements). Use combine for {} and union for [].

Does combine modify the original variable?

No. Jinja2 filters return new values. To update a variable, use set_fact with the combined result: my_dict: "{{ my_dict | combine(new_data) }}".

How do I merge deeply nested dicts without losing keys?

Use recursive=true: {{ dict1 | combine(dict2, recursive=true) }}. Without it, nested dicts are completely replaced by the second dict's version.

Conclusion

Use combine for merging dictionaries with clean defaults/overrides patterns. Always use recursive=true for nested dicts, and list_merge='append' if you need to merge lists inside dicts. The last dict in the chain always wins.

Related Articles

Ansible dict2items & items2dictAnsible Jinja2 FiltersAnsible set_fact

Category: database-automation

Browse all Ansible tutorials · AnsiblePilot Home