# Source of Truth with NetBox

Networks in modern enterprises and service providers are becoming more complex. Network teams handle Cisco IOS, Juniper Junos, Nokia SR OS, firewalls, wireless networks, cloud connectivity, and virtual infrastructure.

Traditional documentation methods, like spreadsheets and static diagrams, are no longer effective at scaling.

This is why modern network automation depends heavily on a **Source of Truth**.

# What is source of truth ?

A Source of Truth is a centralized platform that stores authoritative infrastructure information.

Instead of using:

*   spreadsheets
    
*   text files
    
*   Visio diagrams
    
*   disconnected databases
    

All infrastructure data is stored centrally. This includes:

*   Devices
    
*   IP addresses
    
*   VLANs
    
*   Interfaces
    
*   Sites
    
*   Racks
    
*   VRFs
    

The **Source of Truth** serves as the reliable inventory for monitoring systems, automation tools, CI/CD pipelines, and validation frameworks.

# Why is a Source of Truth Required?

1.  **Manual Documentation Fails** - Spreadsheets and manually maintained inventories quickly become outdated.
    
2.  **Automation Requires Reliable Data** - Automation frameworks such as Ansible, Nornir , pyATS and Terraform depend on accurate infrastructure information.
    
3.  **Multivendor Environments Increase Complexity** - A Source of Truth normalizes infrastructure information across vendors.
    
4.  **Better Operational Visibility** - Network teams can quickly identify Device Locations , Interface usage , IP allocations and VLAN assignments to name a few
    

# What is Netbox ?

NetBox is a purpose-built "source of truth" (SoT) for network and infrastructure inventory, intended to be the definitive system for **DCIM** (data center infrastructure management) and **IPAM** (IP address management). It provides a structured data model, a REST + GraphQL API, change logging, webhooks, and a rich UI — all of which make it ideal as the authoritative dataset driving automation and operational workflows.

## What Makes NetBox Unique?

1.  **API-First Design** - NetBox was designed for automation from the beginning. Everything in NetBox can be accessed using APIs.
    
2.  **Excellent IPAM Capabilities** - NetBox provides IPv4 management , IPv6 management, VRFs , VLAN tracking and Prefix management. This reduces IP conflicts , Duplicate allocations and poor subnet tracking.
    
3.  **Powerful Infrastructure Modeling** - NetBox models real infrastructure relationships.
    
4.  **Dynamic Inventory Generation** - NetBox allows dynamic inventory generation automatically. New devices become available immediately to automation frameworks.
    

## Why Should Network Teams Use NetBox?

1.  **Better Documentation** - NetBox replaces spreadsheets, disconnected inventories and static diagrams with centralized live documentation.
    
2.  **Improved Automation** - NetBox enables Dynamic inventories , Configuration generation , Compliance validation and Automated provisioning
    
3.  **Faster Troubleshooting** - Engineers can quickly identify Device ownership , Interface mappings , VLAN relationships and Rack locations
    
4.  **Scalable Operations** - As networks grow, manual tracking becomes impossible. NetBox enables scalable operational workflows.
    

## Populating Data in NetBox

| Method | Description |
| --- | --- |
| **Manual Object Creation** | The easiest and most straightforward method to populate data in NetBox is by using the object creation forms available in the user interface (UI). |
| **Bulk Import (CSV/YAML)** | NetBox supports the bulk import and updating of objects using CSV-formatted data. This method is ideal for importing spreadsheet data, which can be easily converted to CSV. You can import CSV data either as raw text in the form field or by uploading a correctly formatted CSV file. |
| **Scripting** | You will find that data you need to populate in NetBox can be easily reduced to a pattern. A simple custom script to automatically populate this information can be created |
| **REST API** | The REST API can also be used to populate data in NetBox, providing full programmatic control over object creation while adhering to the same validation rules as the UI forms. Furthermore, it supports the bulk creation of multiple objects with a single request. |

Rather than jumping straight into code, we start where every network team should — with the **NetBox GUI**. Getting your data model right in the UI first means your automation scripts will query clean, consistent, well-structured data.

> The principle of "garbage in, garbage out" is especially relevant to network automation.

# Lab Topology

| Device | Vendor | Os | Site | Mgmt IP | Loopback |
| --- | --- | --- | --- | --- | --- |
| core-rtr-01 | Cisco | IOS-XR | DC-Auckland | 192.168.10.1 | 10.10.10.3/32 |
| core-rtr-02 | Juniper | Junos | DC-Sydney | 192.168.20.1 | 10.10.10.4/32 |
| pe-rtr-01 | Nokia | SR OS | DC-Queenstown | 192.168.30.1 | 10.10.10.2/32 |
| pe-rtr-02 | Nokia | SR OS | DC-Queenstown | 192.168.30.2 | 10.10.10.1/32 |

# Install NetBox and Access the GUI

NetBox operates as a web application supported by PostgreSQL and Redis. The quickest method to obtain a fully functional instance is by using **netbox-docker**, an officially maintained Docker Compose stack that automatically manages all dependencies.

## Installation

Clone netbox-docker and Configure Environment

```python
# Clone the official netbox-docker repo — always use the release branch
git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
```

Create `docker-compose.override.yml` to set port and credentials:

```python
cat > docker-compose.override.yml << 'EOF'
services:
  netbox:
    ports:
      - "8080:8080"
    environment:
      - SUPERUSER_NAME=admin
      - SUPERUSER_EMAIL=devplayground.gmail.com
      - SUPERUSER_PASSWORD=CodedNetbox2026!
      - ALLOWED_HOSTS=*
EOF
```

Pull Images and Start NetBox

```python
# Pull all required images (netbox, postgres, redis, nginx)
docker compose pull

# Start all services in detached mode
docker compose up -d

# Watch startup logs — wait for 'Listening at: http://0.0.0.0:8080'
docker compose logs -f netbox

# Check all containers are healthy
docker compose ps
```

Expected output of `docker compose ps` :

```python
NAME                          STATUS          PORTS
netbox-docker-netbox-1        Up (healthy)    0.0.0.0:8080->8080/tcp
netbox-docker-postgres-1      Up (healthy)    5432/tcp
netbox-docker-redis-1         Up (healthy)    6379/tcp
netbox-docker-worker-1        Up (healthy)
netbox-docker-housekeeping-1  Up (healthy)
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The initial startup takes 3-5 minutes as Docker pulls images and NetBox performs database migrations. Later startups take less than 30 seconds. If 'netbox' remains in 'starting' status for more than 5 minutes, execute 'docker compose logs netbox' to identify any errors.</div>
</div>

## Access the NetBox GUI

Open your browser and navigate to:

```python
http://localhost:8080

# If running on a remote server, replace localhost with the server IP:
http://<host-ip>:8080
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Note</strong> : port 8080 must be opened on the host machine</div>
</div>

### You will see the NetBox login page

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/8ef8a373-f77c-4233-bcd5-586e27936b53.jpg align="center")

### What You See After Logging In

The NetBox dashboard displays a summary of all object counts, including devices, IPs, prefixes, and VLANs. Upon first login, all values will be zero, which is normal. The top navigation bar features six main menus:

| Menu | What Lives Here |
| --- | --- |
| **Organization** | Sites, racks, tenants, contacts, locations |
| **Devices** | Devices, device types, manufacturers, roles, platforms, cables |
| **IPAM** | IP addresses, prefixes, VLANs, RIRs, aggregates, services |
| **Circuits** | Providers, circuits, circuit terminations |
| **Virtualization** | Clusters, virtual machines, VM interfaces |
| **Operations** | Webhooks, event rules, scripts, reports, changelog |
| **Customization** | Custom fields, custom links, export templates, tags |
| **Admin** | Users, groups, API tokens, object permissions |

## Generate an API Token (Required for Later Automation)

**GUI** : Admin → API Tokens → + Add

Although this post emphasizes the GUI, creating a token now will set you up for future automation

| Field | Value |
| --- | --- |
| **User** | admin (your superuser) |
| **Key** | (leave blank — auto-generated) |
| **Write Enabled** | ✓ Checked |
| **Expires** | (leave blank for lab, set expiry for production) |
| **Description** | Lab automation token |

Click **Save**. The token will be displayed only once, so make sure to copy and store it securely.

```python
# Quick API test

curl -X GET \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json; indent=4" \
http://<local-host>:8080/api/status/

# Successful response 

{
    "django-version": "6.0.5",
    "hostname": "4ba0dd1e9137",
    "installed_apps": {
        "django_filters": "25.2",
        "django_prometheus": "2.4.0",
        "django_rq": "4.1.0",
        "django_tables2": "2.8.0",
        "drf_spectacular": "0.29.0",
        "drf_spectacular_sidecar": "2026.5.1",
        "mptt": "0.18.0",
        "rest_framework": "3.17.1",
        "social_django": "5.9.0",
        "taggit": "6.1.0",
        "timezone_field": "7.2.1"
    },
    "netbox-version": "4.6.1",
    "netbox-full-version": "4.6.1-Docker-5.0.1",
    "plugins": {},
    "python-version": "3.14.4",
    "rq-workers-running": 1
```

## Useful Docker Management Commands

| Task | Command |
| --- | --- |
| Stop NetBox | docker compose down |
| Start NetBox | docker compose up -d |
| View live logs | docker compose logs -f netbox |
| Check container health | docker compose ps |
| Restart a single service | docker compose restart netbox |
| Access the NetBox shell | docker compose exec netbox /bin/bash |
| Backup the database | docker compose exec postgres pg\_dump -U netbox netbox > netbox\_backup.sql |

# Organization - Sites , Racks and Location

## Create Sites

**GUI** : Organization → Sites → + Add

Fill in the form for DC-Auckland :

| Field | Value |
| --- | --- |
| **Name** | DC-Auckland |
| **Slug** | dc-auckland (auto-fills) |
| **Status** | Active |
| **ASN** | 65000 |
| **Time Zone** | Pacific/Auckland |
| **Description** | Primary data centre — Auckland |
| **Physical Address** | Auckland, New Zealand |

Click **Save** . Repeat the same process for other sites

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/b530fe64-30ca-41b7-b6a6-f44171241682.jpg align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Tip</strong> : The ASN field on the site is useful for BGP automation. Your scripts can retrieve the correct local-AS directly from NetBox, eliminating the need to hard-code it for each device or playbook.</div>
</div>

## Create Rack Groups and Racks

**GUI** : Organization → Racks → Rack Groups → + Add

| Field | Value |
| --- | --- |
| **Name** | AKL-ROW-A |
| **Slug** | akl-row-a |
| **Site** | DC-Auckland |

**GUI** : Organization → Racks → + Add

| Field | Value |
| --- | --- |
| **Name** | AKL-RACK-01 |
| **Site** | DC-Auckland |
| **Rack Group** | AKL-ROW-A |
| **Status** | Active |
| **Type** | 4-post enclosed |
| **Height (U)** | 42U |

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Note</strong>: When the rack is populated, space utilization is highlighted.</div>
</div>

# Device Types & Manufacturers - Hardware Models, Roles, and Platforms

## Add Manufacturers

**GUI** : Device → Device Types → Manufacturers → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/7791d24b-dd8c-44f5-9f04-c5b780d3aa85.jpg align="center")

## Add Device Types — Cisco xrv9k (IOS-XR)

**GUI** : Device → Device Types → + Add

| Field | Value |
| --- | --- |
| **Manufacturer** | Cisco |
| **Model** | cisco\_xrv9000 |
| **Slug** | cisco\_xrv9000 |
| **Part Number** | 8HE03685AARA01 |
| **Height (U)** | 1 |
| **Full Depth** | ✓ Checked |
| **Comments** | Virtual IOS-XR router |

After saving, click the **Interfaces** tab → **Add Interface Template**:

| Interface Name | Type | Management Only |
| --- | --- | --- |
| GigabitEthernet1 | 1000BASE-T (1GE) | ✓ Yes |
| GigabitEthernet2 | 1000BASE-T (1GE) | No |
| GigabitEthernet3 | 1000BASE-T (1GE) | No |
| Loopback0 | Virtual | No |

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Tip</strong> : Interface templates automatically populate each device created from this type. Your automation scripts can reliably use <code>device.interfaces.all()</code> to find consistent, vendor-accurate interface names, eliminating the need for guessing or hard-coding.</div>
</div>

## Add Device Roles

**GUI** : Device → Device Roles → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/7ab1c4a3-3db0-4014-b357-baa744128cd3.jpg align="center")

## Add Platforms

**GUI** : Device → Platforms → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/98f1c871-a7e8-44eb-97cf-03ff3825e04b.jpg align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Note </strong>: The platform slug is referenced in your Python PLATFORM_MAP as follows: {'ios-xr': 'cisco_xr, 'junos': 'juniper_junos', 'sros': 'nokia_sros'}. Ensure slugs are lowercase and hyphenated, as they are crucial for automation logic.</div>
</div>

# Devices

## Add core-rtr-01 — Cisco IOS-XR (DC-Auckland)

**GUI** : Device → Devices → + Add

| Field | Value |
| --- | --- |
| **Name** | core-rtr-01 |
| **Device Role** | Core Router |
| **Device Type** | cisco\_xrv9000 |
| **Platform** | Cisco IOS-XR |
| **Site** | DC-Auckland |
| **Status** | Active |
| **Rack** | AKL-RACK-01 |

Click **Save** to navigate to the device detail page. Leave the Primary IP blank for now; it will be assigned after creating the IP address record.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Verify: Devices → Devices should now list all four routers.</div>
</div>

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/ddd0063d-5d6a-4c46-92af-b0b60413d6c9.jpg align="center")

# IPAM - RIRs, Prefixes, IP Addresses, and Primary IPs

## Create RIRs

**GUI** : IPAM → Aggregates → RIR → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/2cc4174e-6603-4459-b879-c3e646030d2d.jpg align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>RFC6996</strong> is the correct RIR name for private AS numbers (64512–65534)</div>
</div>

## Create Prefix Roles and Prefixes

**GUI** : IPAM → Prefixes → Prefix & VLAN Roles → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/a8ae01dd-3210-4fa8-839d-de76f8dede5c.jpg align="center")

**GUI** : IPAM → Prefixes → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/e085e3a8-767b-452b-b8b7-5e279d0ef1c0.jpg align="center")

## Add IP Addresses and Assign to Interfaces

**GUI** : IPAM → IP Addresses → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/426ec39c-95bb-405d-a0e4-46cb190cf0ed.jpg align="center")

## Set Primary IP on Each Device

**GUI** : Devices → Devices → Edit

| Field | Value |
| --- | --- |
| **Primary IPv4** | Select the management IP from the dropdown |

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Tip </strong>: The Primary IPv4 field is crucial for automation. The Ansible NetBox inventory plugin relies on primary_ip4 as ansible_host. Without a primary IP, the device is excluded from the inventory and skipped by all playbooks.</div>
</div>

# Custom Fields - Extending Netbox for Automation

## Create Custom Fields

**GUI** : Customization → Custom Fields → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/a5908b61-1dfd-4a93-bdff-4bbd894dd8a7.jpg align="center")

## Populate Custom Fields on Each Device

**GUI** : Devices → Devices → Edit → (scroll to Custom Fields)

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/a2524508-7afb-4bc4-b8bb-38f556ab65fb.jpg align="center")

# Tags - Flexible Multi-Dimensional Grouping

## Create Tags

**GUI** : Customization → Tags → + Add

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/ce068a71-3a17-48ab-9abe-04cfbe8886fa.jpg align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Tags used in ansible playbooks <code>nb.dcim.devices.filter(tag="bgp-peer")</code></div>
</div>

## Apply Tags to Devices

**GUI** : Devices → Devices → Edit → Tags Field

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/6134cd22-d8fc-47f6-9ad7-fe09a328bd25.jpg align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Tip</strong> : Tags can be queried through the API: <code>nb.dcim.devices.filter(tag='bgp-peer') </code>retrieves only BGP-speaking routers. This allows you to write targeted playbooks, such as 'push new BGP password to all devices tagged as bgp-peer,' without needing separate manual lists.</div>
</div>

# Cables - Documenting Physical Connectivity

## Add a Cable Between core-rtr-01 and pe-rtr-01

**GUI** : Devices → Devices (choose site) → Interfaces Tab → Connect (cable-icon)

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/8c6be79c-8729-46f5-a16e-9aaba45009dd.jpg align="center")

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/b7eacf83-7cbe-4c33-bc7b-30bbc5d6262c.jpg align="center")

# Webhooks & Event Rules - GUI Configured Event-Driven Automation

## Create a Webhook

**GUI** : Integrations → Webhook → + Add

| Field | Value |
| --- | --- |
| **Name** | device-change-to-automation |
| **URL** | http://your-automation-host:9000/webhook/netbox |
| **HTTP Method** | POST |
| **HTTP Content Type** | application/json |
| **Secret** | <your-secret> |
| **SSL Verification** | Unchecked for lab ✓ Checked for production |

## Create Event Rules

**GUI** : Integrations → Event Rules → + Add

### Rule 2 — Device Status Changed to Active (with Condition):

![](https://cdn.hashnode.com/uploads/covers/68933f4690103a1d4a8d7df7/64244322-6177-48da-941c-199af6f41b9b.jpg align="center")

In the **Conditions** field, the below JSON is used to filter on status change:

```json
{
  "and": [
    {
      "attr": "status.value",
      "value": "active"
    }
  ]
}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Tip</strong> : The Conditions filter helps prevent webhook spam with every edit. Without it, any change to a device, even minor ones like adding a comment, would trigger the webhook. Use conditions to focus on the specific state transitions that are relevant to your automation.</div>
</div>

## Test the Webhook with a Quick Listener

In a terminal, start a one-liner HTTP listener:

```python
python3 -c "
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        raw = self.rfile.read(length)
        data = json.loads(raw)
        print(json.dumps(data, indent=2))
        self.send_response(200)
        self.end_headers()
    def log_message(self, *args):
        pass

print('Listening on port 9000...')
HTTPServer(('', 9000), Handler).serve_forever()
"
```

In the NetBox GUI, edit **pe-rtr-02**, change Status from Active → Planned → Active. Expected output:

```python
Listening on port 9000...
{
  "event": "updated",
  "timestamp": "2026-05-27T01:53:21.485685+00:00",
  "object_type": "dcim.device",
  "username": "admin",
  "request_id": "21724c08-cb44-4961-bde1-f76fd4d9fd89",
  "data": {
    "id": 4,
    "url": "/api/dcim/devices/4/",
    "display_url": "/dcim/devices/4/",
    "display": "pe-rtr-02",
    "name": "pe-rtr-02",
    "device_type": {
      "id": 3,
      "url": "/api/dcim/device-types/3/",
      "display": "7750 SR-1",
      "manufacturer": {
        "id": 2,
        "url": "/api/dcim/manufacturers/2/",
        "display": "nokia",
        "name": "nokia",
        "slug": "nokia",
        "description": ""
      },
      "model": "7750 SR-1",
      "slug": "7750-sr-1",
      "description": "",
      "device_count": 2
    },
```

# My personal opinion about NetBox

For a long time, I assumed NetBox was exclusively for production use, primarily employed by enterprise teams to document campus networks and validate their IPAM budgets. I considered it unnecessary for my lab, thinking it was "just a lab."

Then I began using containerlab in a more serious manner.

## The Problem Nobody Talks About

Containerlab is truly impressive. You create a YAML topology file, execute containerlab deploy, and in less than a minute, you have a fully connected network of Cisco XRv9000s, Juniper vMX routers, and Nokia SR OS nodes communicating seamlessly. The first experience feels magical.

But here's what happens after the first time.

You create a topology for BGP testing, then another for MPLS, followed by one for SR-MPLS, and a data center fabric. When someone requests you to replicate a production issue, you quickly set up a topology that somewhat resembles it. Soon, you find yourself with six topology files in different stages of completion, a folder full of configurations with names like `final_v2_actually_final.cfg`, and no clear idea of which IP addresses were used where.

You SSH into something and get a different device than you expected. You run an Ansible playbook and it hits the wrong node. You try to reproduce a test from two weeks ago and spend an hour figuring out what the management IPs were.

This is the **source-of-truth** problem. And it doesn't only affect production networks.

## Why Netbox matters ?

A Source of Truth is now essential in modern networking. NetBox offers the centralized, automation-ready infrastructure platform needed for multi-vendor environments in my case. Modern automation starts with reliable infrastructure data, and NetBox provides that foundation.
