Creating Zabbix Network Maps with Ansible
Managing Zabbix network maps by clicking through the frontend works fine for a handful of maps, but it does not scale — and it is not reproducible. This guide shows how to define maps as code in a YAML file and create them automatically using Ansible and the community.zabbix collection.
How it works
The community.zabbix.zabbix_map module takes a graph description written in Graphviz DOT language, calls the Zabbix API, and creates the map. Graphviz handles the layout automatically, so you describe what is connected rather than where every element sits on the canvas.
The workflow is:
- Define all maps in a single
maps.yamlfile using DOT syntax - Run the playbook — it reads the file and creates or updates each map via the API
- Commit the YAML to version control and treat your maps like any other infrastructure config
Prerequisites
Ansible control node:
ansible-galaxy collection install community.zabbix
apt install graphviz # Debian/Ubuntu
# dnf install graphviz # RHEL/Rocky
Zabbix: a service account with permissions to create maps.
Directory structure
zabbix-maps/
├── create_zabbix_maps.yml # Playbook
├── maps.yaml # Map definitions
└── group_vars/all/
└── zabbix.yml # Credentials (vault-encrypted)
Credentials
Store credentials in group_vars/all/zabbix.yml and encrypt the file with Ansible Vault:
# group_vars/all/zabbix.yml
zabbix_url: "https://zabbix.example.com"
zabbix_user: "ansible-svc"
zabbix_password: "your_password_here"
ansible-vault encrypt group_vars/all/zabbix.yml
The playbook
# create_zabbix_maps.yml
- name: Create Zabbix network maps from YAML templates
hosts: localhost
connection: local
gather_facts: false
vars:
maps_file: "maps.yaml"
map_name: "" # Filter to a single map; empty = all
module_defaults:
community.zabbix.zabbix_map:
server_url: "{{ zabbix_url }}"
login_user: "{{ zabbix_user | default(omit) }}"
login_password: "{{ zabbix_password | default(omit) }}"
tasks:
- name: Load map definitions
ansible.builtin.include_vars:
file: "{{ maps_file }}"
name: maps_config
- name: Filter maps (when map_name is set)
ansible.builtin.set_fact:
maps_to_process: >-
{{
maps_config.maps
if not map_name
else maps_config.maps | selectattr('name', 'equalto', map_name) | list
}}
- name: Create or update Zabbix maps
community.zabbix.zabbix_map:
name: "{{ item.name }}"
width: "{{ item.width | default(1200) }}"
height: "{{ item.height | default(800) }}"
state: "{{ item.state | default('present') }}"
data: "{{ item.data }}"
default_image: "Server_(48)"
expand_problem: true
loop: "{{ maps_to_process }}"
loop_control:
label: "{{ item.name }}"
Defining maps in YAML
Each map entry has a name, optional width/height, and a data block containing the DOT graph.
Nodes become map elements. Use zbx_* attributes to attach Zabbix context:
| Attribute | Description |
|---|---|
zbx_host | Zabbix host name — makes the node a host element |
zbx_group | Zabbix host group name |
zbx_map | Drill-down to another Zabbix map |
zbx_image_default | Icon shown in OK state |
zbx_image_problem | Icon shown when there are active problems |
zbx_image_maintenance | Icon shown during maintenance |
zbx_image_disabled | Icon shown when the host is disabled |
Edges become links. Use these attributes to style them:
| Attribute | Values |
|---|---|
label | Text shown on the link |
zbx_draw_style | line | bold | dotted | dashed |
zbx_color | CSS3 name or hex (#FF0000) |
Icon names must match exactly what is in Administration → Images in your Zabbix instance. The built-in icons use names like
Server_(48),Router_(64),Database_(48)— verify the exact names in your environment.
Example: Zabbix infrastructure map
A practical first map to create is the Zabbix monitoring infrastructure itself — the PostgreSQL database, the Zabbix server, and all proxies. It gives you an at-a-glance view of the backbone of your monitoring stack.
# maps.yaml
maps:
- name: "Zabbix Infrastructure"
width: 1400
height: 800
state: present
data: |
graph {
// Database
"PostgreSQL" [zbx_host="db-zabbix-01"
zbx_image_default="Database_(64)"
zbx_image_problem="Database_(64)"]
// Zabbix server
"Zabbix Server" [zbx_host="zabbix-server-01"
zbx_image_default="Server_(64)"
zbx_image_problem="Server_(64)"]
// Active proxies
"Proxy CPH-01" [zbx_host="zabbix-proxy-cph-01"
zbx_image_default="Server_(48)"
zbx_image_problem="Server_(48)"]
"Proxy CPH-02" [zbx_host="zabbix-proxy-cph-02"
zbx_image_default="Server_(48)"
zbx_image_problem="Server_(48)"]
"Proxy AAR-01" [zbx_host="zabbix-proxy-aar-01"
zbx_image_default="Server_(48)"
zbx_image_problem="Server_(48)"]
// Links — database to server
"PostgreSQL" -- "Zabbix Server" [label="5432"
zbx_draw_style=bold
zbx_color="#0066CC"]
// Links — server to proxies
"Zabbix Server" -- "Proxy CPH-01" [label="10051"
zbx_draw_style=line
zbx_color="#00AA00"]
"Zabbix Server" -- "Proxy CPH-02" [label="10051"
zbx_draw_style=line
zbx_color="#00AA00"]
"Zabbix Server" -- "Proxy AAR-01" [label="10051"
zbx_draw_style=line
zbx_color="#00AA00"]
}
The result is a clean three-tier diagram: PostgreSQL at the top feeding the Zabbix server, with the server fanning out to proxies. Graphviz picks a sensible hierarchical layout automatically because of the dot engine default.
Running the playbook
# Create all maps
ansible-playbook create_zabbix_maps.yml --ask-vault-pass
# Dry-run — resolve and validate without making changes
ansible-playbook create_zabbix_maps.yml --check --ask-vault-pass
# Create a single map by name
ansible-playbook create_zabbix_maps.yml -e "map_name='Zabbix Infrastructure'" --ask-vault-pass
# Use a different maps file
ansible-playbook create_zabbix_maps.yml -e maps_file=prod-maps.yaml --ask-vault-pass
Adding more maps
Just append more entries to maps.yaml. Each entry is independent — you can have as many maps in the file as you like and they will all be created in a single playbook run.
maps:
- name: "Zabbix Infrastructure"
# ...
- name: "Core Network"
width: 1600
height: 900
state: present
data: |
graph {
"Core Router" [zbx_host="router-core-01"
zbx_image_default="Router_(64)"
zbx_image_problem="Router_(64)"]
# ...
}
To remove a map, set state: absent and re-run the playbook.
Limitations
- No manual positioning. Graphviz determines where elements are placed. For most topology maps this is fine — for maps that need to match a physical floor plan or a specific diagram layout, the
community.zabbixmodule is not the right tool. - No API token support. The module authenticates with username and password only. If your security policy requires token-based authentication, you need a custom module approach instead.
- Graphviz required on the control node. It is a lightweight package, but it is a dependency to be aware of in air-gapped environments.