← POST

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:

  1. Define all maps in a single maps.yaml file using DOT syntax
  2. Run the playbook — it reads the file and creates or updates each map via the API
  3. 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:

AttributeDescription
zbx_hostZabbix host name — makes the node a host element
zbx_groupZabbix host group name
zbx_mapDrill-down to another Zabbix map
zbx_image_defaultIcon shown in OK state
zbx_image_problemIcon shown when there are active problems
zbx_image_maintenanceIcon shown during maintenance
zbx_image_disabledIcon shown when the host is disabled

Edges become links. Use these attributes to style them:

AttributeValues
labelText shown on the link
zbx_draw_styleline | bold | dotted | dashed
zbx_colorCSS3 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.zabbix module 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.