Searching for a tool to keep track of IP addresses; I stumbled onto Netbox — and was blown away by what it could do 🤯 I could put everything there: racks, servers, patch panels, Wi-Fi access points, power distribution, network runs. Everything!

Table of contents

Introduction

I started using Netbox in February, it was easy to deploy — using the Docker image.

Twitter screenshot

Only a few hours in; I concluded that I could pretty much put everything in Netbox. To keep track of my homelab rack, all servers and their inventory, and the entire house network.

Twitter screenshot

So; I started entering data…

Using Netbox

There was a lot of data to input: roles, manufacturers, devices, interfaces, ports, and the list goes on. I spent a few days getting everything in the homelab rack into Netbox.

Screenshot of homelab rack in Netbox

After playing with the device inventory — I decided to use it to keep track of every component inside my computers. So I spend a few days doing that. Registering disks with their bay location and serial number was especially useful 🙂

Screenshot of computer inventory in Netbox

I did some tests, adding interfaces to devices, front and rear ports to patch panels, and connecting cables. It was really cool to be able to trace the network runs all the way from device to rack 👇

Screenshot of cable trace in Netbox

After the initial excitement of finding a new tool had settled — I realized that it would be a lot of work getting everything into Netbox. And probably even more work keeping everything updated…

But my attitude was that the benefit outweighed the cost, I needed to keep track of this data somehow — might as well be Netbox.

Data source for this blog

On this blog I have pretty detailed information on my homelab rack, and all my computers. I now had that information two places: this blog and Netbox. I decided to pull data from Netbox to this blog, keeping a single source of truth 🙂

First I defined some Netbox settings in my Hugo configuration file:

# NetBox settings
[params.netbox]
  url = "http://omicron:8001/api"
  token = "super-secret-token"

Homelab page

For the homelab rack; I got information on front, rear, and non-racked devices — as well as location, name, and height of the rack.

Screenshot of homelab page, data from Netbox

This is the Hugo layout 👇 I’m not going to explain it in detail, if you know Hugo templating and look at the Netbox REST API — it should make some sense to you 🙂

{{- $netboxUrl := $.Site.Params.Netbox.Url -}}
{{- $netboxId := 1 -}}
{{- $netboxApi := dict "Authorization" (printf "Token %s" $.Site.Params.Netbox.Token) -}}

{{- $data := getJSON (printf "%s/dcim/racks/%d/" $netboxUrl $netboxId) $netboxApi  -}}

{{- with $data -}}
<p>
  {{ .location.name }}  {{ .name -}},
  {{ .u_height }}U
</p>
{{- end -}}

{{- with $data.comments -}}
  <div>{{ . | markdownify }}</div>
{{- end -}}

Most of my <a href="{{ ref $ "/computers" }}">computer</a> projects includes, or revolves around — my homelab.

<h2 id="top-to-bottom">Top to bottom {{ partial "headerlink.html" "top-to-bottom" }}</h2>

{{- $inv_all := getJSON (printf "%s/dcim/devices/?rack_id=%d&status=active&status=offline" $netboxUrl $netboxId) $netboxApi -}}

<div>
  <h3 id="front">Front {{ partial "headerlink.html" "front" }}</h3>
  <ul class="computer-inventory-list">
  {{- range sort $inv_all.results "position" "desc" -}}
    {{- if eq (index . "face").label "Front" -}}
    <li>
      {{- if .name -}}
        <a href="{{ ref $ (printf "/computers/%s" (.name|urlize)) }}">{{ .display }}, {{ .device_role.name }}</a>
        {{- with .platform }}
          ({{ .display }})
        {{- end -}}
        <ul><li>
          {{- if ne .device_type.manufacturer.slug "self" }}
            {{ .device_type.manufacturer.name }}
          {{- end }}
          {{ .device_type.model }}
        </li></ul>
      {{- else -}}
        {{- if not (in (slice "self" "generic") .device_type.manufacturer.slug) }}
          {{ .device_type.manufacturer.name }}
        {{- end }}
        {{ .device_type.model }} <code>{{ .device_role.name }}</code>
      {{- end -}}
    </li>
    {{- end -}}
  {{- end -}}
  </ul>

  <h3 id="rear">Rear {{ partial "headerlink.html" "rear" }}</h3>
  <ul class="computer-inventory-list">
  {{- range sort $inv_all.results "position" "desc" -}}
    {{- if eq (index . "face").label "Rear" -}}
    <li>
      {{- if .name -}}
        {{ .name }}, {{ .device_role.name }}
        {{- with .platform }}
          ({{ .display }})
        {{- end -}}
        <ul><li>
          {{- if ne .device_type.manufacturer.slug "self" }}
            {{ .device_type.manufacturer.name }}
          {{- end }}
          {{ .device_type.model }}
        </li></ul>
      {{- else -}}
        {{- if not (in (slice "self" "generic") .device_type.manufacturer.slug) }}
          {{ .device_type.manufacturer.name }}
        {{- end }}
        {{ .device_type.model }} <code>{{ .device_role.name }}</code>
      {{- end -}}
    </li>
    {{- end -}}
  {{- end -}}
  </ul>

  <h3 id="non-racked">Non-racked {{ partial "headerlink.html" "non-racked" }}</h3>
  <ul class="computer-inventory-list">
  {{- range sort $inv_all.results "position" "desc" -}}
    {{- if eq (index . "face") nil -}}
    <li>
      {{- if .name -}}
        {{ .name }}, {{ .device_role.name }}
        {{- with .platform }}
          ({{ .display }})
        {{- end -}}
        <ul><li>
          {{- if ne .device_type.manufacturer.slug "self" }}
            {{ .device_type.manufacturer.name }}
          {{- end }}
          {{ .device_type.model }}
        </li></ul>
      {{- else -}}
        {{- if not (in (slice "self" "generic") .device_type.manufacturer.slug) }}
          {{ .device_type.manufacturer.name }}
        {{- end }}
        {{ .device_type.model }} <code>{{ .device_role.name }}</code>
      {{- end -}}
    </li>
    {{- end -}}
  {{- end -}}
  </ul>
</div>

The layout is pretty verbose… I did plan to refactor the three sections into one, but I never got around to it.

Computer pages

Next it was the inventory for all computers. I pulled the entire inventory, and displayed it in its hierarchical structure — as well as the computer device type, model, name, operating system, and location.

Screenshot of computer page, data from Netbox

This is the Hugo layout 👇 I’m still not going to explain it in detail 🙂

{{- $netboxUrl := $.Site.Params.Netbox.Url -}}
{{- $netboxId := .Params.NetBoxId -}}
{{- $netboxApi := dict "Authorization" (printf "Token %s" $.Site.Params.Netbox.Token) -}}

{{- $data := getJSON (printf "%s/dcim/devices/%d/" $netboxUrl $netboxId) $netboxApi  -}}

{{- with $data -}}
  <p>
    {{- if ne .device_type.manufacturer.slug "self" -}}
      {{ .device_type.manufacturer.name }}
    {{ end -}}
    {{ .device_type.model }}    {{ .device_role.name -}}
    {{- with .platform -}}
      ; {{ .name }}
    {{- end -}}
  </p>
  {{- with .rack }}
    {{- if eq (int .id) 1 -}}
      <p>Located in my <a href="{{ ref $ "/homelab" }}">homelab</a> rack.</p>
    {{- end -}}
  {{- end -}}
{{- end -}}

{{- with $data.comments -}}
  <div>{{ . | markdownify }}</div>
{{- end -}}

<h2 id="inventory">Inventory {{ partial "headerlink.html" "inventory" }}</h2>

{{- $inv_all := getJSON (printf "%s/dcim/inventory-items/?device_id=%d" $netboxUrl $netboxId) $netboxApi -}}

<div>
  <ul class="computer-inventory-list">
  {{- range $role := (slice "CPU" "Motherboard" "Memory" "Graphics" "Controller" "Disk" "Network" "Wi-Fi" "ZFS pool" "Power" "Case" "Input device" "Audio") -}}
  {{- $inv_role := getJSON (printf "%s/dcim/inventory-items/?device_id=%d&role=%s" $netboxUrl $netboxId (.|urlize)) $netboxApi -}}
    {{- range where $inv_role.results "parent" nil -}}
    <li>
      <strong>{{ .role.name }}:</strong> {{ partial "computer-inventory.html" . }}
      {{- with .description -}}
        <ul><li>{{ . }}</li></ul>
      {{- end -}}
      {{- $sub := where $inv_all.results "parent" .id }}

      {{- with $sub -}}
        <ul>{{- range sort . "name" -}}
        <li>{{ partial "computer-inventory.html" . }}
          {{- $sub2 := where $inv_all.results "parent" .id -}}

          {{- with $sub2 -}}
            <ul>{{- range sort . "name" -}}
              <li>{{ partial "computer-inventory.html" . }}
              {{- $sub3 := where $inv_all.results "parent" .id -}}

              {{- with $sub3 -}}
                <ul>{{- range sort . "name" -}}
                  <li>{{ partial "computer-inventory.html" . }}
                {{- end -}}</ul>
              {{- end -}}</li>

            {{- end -}}</ul>
          {{- end -}}</li>

        {{- end -}}</ul>
      {{- end -}}

    </li>
    {{- end -}}
  {{- end -}}
  </ul>
</div>

The nested structure of the inventory made this layout verbose as well… I made a partial, computer-inventory.html, to abstract some repeating code:

{{- with .manufacturer -}}
  {{ .name }}
{{- end }}
{{ .name | replaceRE " [0-9]$" "" }}
{{ with .part_id -}}
  ({{ . }})
{{ end -}}
{{- if ne .parent nil -}}
  {{- with .role -}}
    <code>{{ .name }}</code>
  {{- end -}}
{{- end -}}

To connect a computer page with the correct device in Netbox, I added a netBoxId to the front matter of all computer pages.

+++
title = "Desktop: Sigma"
linkTitle = "Sigma"
computerGroup = "client"
computerType = "desktop"
netBoxId = 17
+++

Conclusion time

I really liked Netbox, and if I had a data center with multiple racks, tenants, circuits, power feeds, etc I would definitely use it. But sadly — I don’t. Netbox for my limited use case was just way overkill and too cumbersome to keep up to date. When inputting data; I often had to go back and create some mandatory field — like role, and manufacturer.

Since I set up Netbox in February and spent some time inputting data — I really haven’t touched it. Any changes I have made have gone undocumented. For me; low friction is important to ensure that I actually update the documentation.

I did sit down last week to update the inventory of a couple of servers, but found that moving inventory items between devices was not as easy as I thought. Mainly due to my use of parent and child parts, which could not be moved.

Even though Netbox is a beast — keeping it updated was easy, just pull the latest Docker image. Except that one time from 3.3 to 3.4, where I had to export and reimport the database.

So, to sum it up: I liked the idea of using Netbox, more than I liked using it. It just wasn’t the right tool for me, even as a data source for pages on this blog. There are simpler tools that does the job just fine 🙂

What now?

So what now? As I said initially:

(…) I needed to keep track of this data somehow — might as well be Netbox.

I do need to document this stuff, but instead of everything in one mammoth tool — I now use:

With Obsidian I have a more pragmatic approach, and I can make it as simple or complex as I need. Everything is stored in local Markdown files, synced between all my devices.

Screenshot of patch panel overview in Obsidian
I’ll write more on how I use data files to build the homelab and computer inventory — in a later blog post.

🖖

Last commit 2024-04-05, with message: Tag cleanup.