Introduction

NetBox is the leading open-source platform for network infrastructure modeling — DCIM (data center infrastructure management) and IPAM (IP address management). The netbox.netbox Ansible collection provides a dynamic inventory plugin that pulls device and VM data directly from NetBox, plus modules to manage every NetBox object. This makes NetBox your single source of truth and Ansible your automation engine.

Prerequisites

# Install the collection
ansible-galaxy collection install netbox.netbox

# Install Python dependency
pip install pynetbox

NetBox API Token

  1. In NetBox: Admin → API Tokens → Add Token
  2. Set permissions (read-only for inventory, read-write for modules)
  3. Save the token

Dynamic Inventory from NetBox

Basic Configuration

# netbox_inventory.yml
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.example.com
token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
validate_certs: true

# Group devices by these attributes
group_by:
  - site
  - role
  - platform
  - device_type

# Use primary IP as ansible_host
compose:
  ansible_host: primary_ip4.address | default('') | ansible.utils.ipaddr('address')
# Set token
export NETBOX_TOKEN=abcdef1234567890

# Test inventory
ansible-inventory -i netbox_inventory.yml --list

# Ping all devices
ansible -i netbox_inventory.yml all -m ping

Advanced Inventory Configuration

# netbox_inventory.yml
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.example.com
token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
validate_certs: true

# Filter devices
query_filters:
  - status: active
  - region: us-east
  - has_primary_ip: true

# Exclude specific roles
exclude_query_filters:
  - role: out-of-band

# Group by multiple attributes
group_by:
  - site
  - role
  - platform
  - tenant
  - tags

# Custom groups from device attributes
groups:
  webservers: "'web' in device_role.slug"
  switches: "'switch' in device_role.slug"
  firewalls: "'firewall' in device_role.slug"
  production: "'production' in tags | map(attribute='slug')"

# Set connection variables from NetBox data
compose:
  ansible_host: primary_ip4.address | ansible.utils.ipaddr('address')
  ansible_network_os: platform.slug | default('')
  ansible_user: "'admin' if 'network' in device_role.slug else 'ansible'"

# Include virtual machines too
virtual_machines: true

# Cache for performance
cache: true
cache_plugin: jsonfile
cache_connection: /tmp/netbox-inventory-cache
cache_timeout: 3600

Test Inventory Groups

# List all groups
ansible-inventory -i netbox_inventory.yml --graph

# Show specific group
ansible-inventory -i netbox_inventory.yml --graph webservers

# Show host variables
ansible-inventory -i netbox_inventory.yml --host switch01

Manage NetBox Objects with Ansible

Create Sites

---
- name: Manage NetBox sites
  hosts: localhost
  gather_facts: false
  vars:
    netbox_url: https://netbox.example.com
    netbox_token: "{{ vault_netbox_token }}"
  tasks:
    - name: Create sites
      netbox.netbox.netbox_site:
        netbox_url: "{{ netbox_url }}"
        netbox_token: "{{ netbox_token }}"
        data:
          name: "{{ item.name }}"
          slug: "{{ item.slug }}"
          status: active
          region: "{{ item.region }}"
          facility: "{{ item.facility }}"
          time_zone: "{{ item.tz }}"
        state: present
      loop:
        - { name: "US-East-1", slug: "us-east-1", region: "US East", facility: "EQX-DC5", tz: "America/New_York" }
        - { name: "EU-West-1", slug: "eu-west-1", region: "EU West", facility: "EQX-LD8", tz: "Europe/London" }

Create Devices

- name: Create network devices
  netbox.netbox.netbox_device:
    netbox_url: "{{ netbox_url }}"
    netbox_token: "{{ netbox_token }}"
    data:
      name: "{{ item.name }}"
      device_type: "{{ item.type }}"
      role: "{{ item.role }}"
      site: "{{ item.site }}"
      status: active
      platform: "{{ item.platform }}"
      serial: "{{ item.serial }}"
      tags:
        - production
        - managed
    state: present
  loop:
    - { name: "core-sw01", type: "Cisco Catalyst 9300", role: "Core Switch", site: "US-East-1", platform: "cisco-ios", serial: "FCW2345G0AB" }
    - { name: "fw01", type: "Palo Alto PA-3260", role: "Firewall", site: "US-East-1", platform: "panos", serial: "012345678" }

Manage IP Addresses

- name: Create IP prefix
  netbox.netbox.netbox_prefix:
    netbox_url: "{{ netbox_url }}"
    netbox_token: "{{ netbox_token }}"
    data:
      prefix: 10.0.0.0/24
      site: US-East-1
      vlan:
        name: Management
      status: active
      description: "Management network"
    state: present

- name: Assign IP addresses
  netbox.netbox.netbox_ip_address:
    netbox_url: "{{ netbox_url }}"
    netbox_token: "{{ netbox_token }}"
    data:
      address: "{{ item.ip }}"
      status: active
      assigned_object:
        device: "{{ item.device }}"
        name: "{{ item.interface }}"
      dns_name: "{{ item.dns }}"
    state: present
  loop:
    - { ip: "10.0.0.1/24", device: "core-sw01", interface: "Vlan100", dns: "core-sw01.example.com" }
    - { ip: "10.0.0.2/24", device: "fw01", interface: "ethernet1/1", dns: "fw01.example.com" }

Manage VLANs

- name: Create VLANs
  netbox.netbox.netbox_vlan:
    netbox_url: "{{ netbox_url }}"
    netbox_token: "{{ netbox_token }}"
    data:
      name: "{{ item.name }}"
      vid: "{{ item.vid }}"
      site: US-East-1
      status: active
      description: "{{ item.desc }}"
    state: present
  loop:
    - { name: "Management", vid: 100, desc: "Management network" }
    - { name: "Servers", vid: 200, desc: "Server production" }
    - { name: "Users", vid: 300, desc: "User workstations" }
    - { name: "DMZ", vid: 400, desc: "DMZ network" }

Full Workflow: NetBox → Ansible → Network

---
# Step 1: Query NetBox for VLAN assignments
- name: Configure switches from NetBox source of truth
  hosts: switches
  gather_facts: false
  vars:
    netbox_url: https://netbox.example.com
    netbox_token: "{{ vault_netbox_token }}"
  tasks:
    - name: Get VLANs from NetBox
      netbox.netbox.netbox_vlan:
        netbox_url: "{{ netbox_url }}"
        netbox_token: "{{ netbox_token }}"
        data:
          site: "{{ site }}"
        state: present
      register: vlans

    - name: Configure VLANs on switch
      cisco.ios.ios_vlans:
        config:
          - name: "{{ item.name }}"
            vlan_id: "{{ item.vid }}"
            state: active
        state: merged
      loop: "{{ vlans.results | default([]) }}"

Lookup Plugin

Query NetBox from within playbooks:

- name: Get device info from NetBox
  ansible.builtin.debug:
    msg: "{{ lookup('netbox.netbox.nb_lookup', 'devices', api_filter='name=core-sw01', api_endpoint=netbox_url, token=netbox_token) }}"

Troubleshooting

"pynetbox" Not Found

pip install pynetbox

Empty Inventory

  • Check query_filters — has_primary_ip: true excludes devices without IPs
  • Verify API token permissions
  • Check device status: active in NetBox

SSL Certificate Errors

# For self-signed certs (dev only)
validate_certs: false

Best Practices

  1. NetBox is the source of truth — never hardcode IPs or device lists in Ansible
  2. Use caching — NetBox inventory queries can be slow; cache for 1 hour
  3. Read-only tokens for inventory — minimize API token permissions
  4. Tag-based groups — use NetBox tags for dynamic grouping
  5. Sync both ways — update NetBox after Ansible makes changes
  6. Version your NetBox data — use NetBox's changelog and Ansible to audit changes

Conclusion

NetBox plus Ansible creates a closed loop: NetBox holds the intended state of your infrastructure (devices, IPs, VLANs, prefixes), the dynamic inventory plugin feeds that data to Ansible, and Ansible modules configure the actual network to match. Use the nb_inventory plugin with group_by and compose for automatic host grouping and connection variables, and the netbox_* modules to keep NetBox in sync as your infrastructure evolves.