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 & items2dict • Ansible Jinja2 Filters • Ansible set_factCategory: database-automation