Alles in dit stuk is uitgevoerd tegen een NetBox 4.7.2 op mijn eigen bureau, met een echt bestand erin geladen: één site, één kast, veertien apparaten, gekabeld, van stroom voorzien en geadresseerd over drie klanten. Elke schermafbeelding is die instantie, en elke foutmelding is er een die ik echt heb gekregen.
Het gaat in de volgorde waarin je het zou tegenkomen. Wat het ding is, waarom je er een zou willen, de fork waar je binnen een week zoeken over hoort, hoe je er een opzet, de volgorde waarin je het moet vullen, en dan wat je eruit terugkrijgt. Het aanpas- en uitbreidwerk staat aan het eind, omdat niets daarvan betekenis heeft voordat je de vorm hebt gezien van wat je uitbreidt.
Wat NetBox is
NetBox is een database met een heel specifieke mening over waar een netwerk uit bestaat, en een webapplicatie daarboven. Het is een Django-applicatie op PostgreSQL, het is open source onder Apache 2.0 sinds DigitalOcean het in juni 2016 vrijgaf, en het project wordt vandaag beheerd door NetBox Labs samen met een team vrijwillige maintainers.12
Daaronder zijn het 149 modellen over tien applicaties, bereikbaar via 146 REST-endpoints en één GraphQL-endpoint. Geteld op de instantie die ik hiervoor heb gebouwd, niet afgelezen van een featurepagina:
| Applicatie | Modellen | Applicatie | Modellen |
|---|---|---|---|
| dcim | 56 | virtualization | 7 |
| extras | 23 | tenancy | 6 |
| ipam | 18 | wireless | 3 |
| circuits | 11 | users | 7 |
| vpn | 10 | core | 8 |
Zesenvijftig daarvan zijn DCIM, de fysieke laag: sites, locaties, racks, apparaattypes, apparaten, en elk soort poort, bay en kabelafsluiting die een apparaat kan hebben. Achttien zijn IPAM. De rest dekt circuits, tunnels en IKE-policies, virtuele machines en clusters, draadloze verbindingen, tenancy, en de machinerie die het geheel uitbreidbaar maakt.
Het aantal is niet het punt. De joins zijn dat, en de snelste manier om dat te zien is het scherm waar NetBox het bekendst om is.
Begin bij de kast, want dat is het scherm dat het ding verkoopt.

Die elevatie wordt uit de data getekend, niet geüpload. Elk apparaat staat er omdat iets zegt dat het die rack-units bezet, met die kant naar voren, en de kleuren komen van de rol die je het gegeven hebt. De ruimtebenutting leest 28,6% omdat NetBox het heeft uitgerekend. Niemand onderhoudt dat getal.
Er volgen twee dingen uit die meer waard zijn dan het plaatje.
Je kunt vragen welke units vrij zijn en een antwoord krijgen waar je mee verder kunt. Je kunt ook units reserveren voordat er iets in is gebouwd, en dat is het verschil tussen ruimte verkopen die je hebt en ruimte verkopen waarvan je denkt dat je die hebt.
Wat het bewust niet doet
Een product dat weet wat het niet is, is zeldzamer dan een product dat alles slecht doet, en NetBox’ eigen documentatie is er onomwonden over. Het levert geen netwerkmonitoring, geen DNS-dienst, geen RADIUS, geen configuratiebeheer en geen facilitair beheer.1
Belangrijker nog: het bevat de gewenste toestand van je netwerk, niet de operationele toestand, en de documentatie zegt dat geautomatiseerde import van de levende netwerktoestand “strongly discouraged” is, omdat elk record eerst door een mens gecontroleerd moet worden.1
Dat is de beslissing waarop al het andere rust, en het is de beslissing waar mensen over in discussie gaan. Het argument gaat zo: een source of truth zou toch de waarheid moeten zijn, dus ontdek het netwerk en laad het in. Het antwoord is dat een ontdekt netwerk je vertelt wat er is, en wat er is omvat elke fout die ooit iemand heeft gemaakt. Een switchpoort die in 2021 in het verkeerde VLAN is gelaten, is een feit. Het is geen bedoeling.
NetBox bevat de bedoeling. Je monitoring bevat de werkelijkheid. Het interessante getal is het verschil daartussen, en een verschil kun je niet uit één invoer berekenen.
Het andere uitgangspunt staat er net zo helder: als je kunt kiezen tussen een relatief eenvoudige tachtigprocentoplossing en een veel complexere complete, neem dan de eenvoudige.1 Dat voel je de eerste keer dat je iets wilt modelleren wat het niet modelleert, en er staat tegen het eind een hele reeks secties over wat je dan doet.
| NetBox doet | NetBox doet niet |
|---|---|
| Vastleggen wat er zou moeten staan | Opvragen wat er staat |
| Het bedoelde VLAN voor een poort bevatten | Je vertellen dat de poort eruit ligt |
| Zeggen van welke klant een prefix is | Het hem factureren |
| De configuratie van een apparaat uit een template renderen | Die naar het apparaat pushen |
| Het circuit, de provider en de commit bijhouden | Het circuit monitoren |
| Zeggen in welk rack een apparaat staat, en in welke U | De kast opendoen |
Lees die rechterkolom als een lijst van gereedschap dat je nog steeds nodig hebt. Lees hem verkeerd en je gaat proberen NetBox al dat gereedschap te laten zijn, en zo wordt een source of truth nog een systeem waar niemand op vertrouwt.
Waarom je er een nodig hebt
Vraag een managed service provider waar het gezaghebbende record van het netwerk van een klant staat, en je krijgt een antwoord. Vraag het twee van hun engineers apart en je krijgt er twee.
Eén wijst naar een spreadsheet. Eén wijst naar een schema dat het laatst is opgeslagen door iemand die in 2023 is weggegaan. Nog iemand zegt dat de firewallconfiguratie de documentatie is, wat tenminste eerlijk is, want een configuratie beschrijft wel degelijk wat een kast doet. Hij beschrijft alleen niet waarom, of wie erom heeft gevraagd, of welke van de vier klanten achter die kast voor de regel betaalt.
Het falen is nooit de dag waarop je merkt dat het record verkeerd is. Het is de dag waarop iemand het nodig heeft.
| Het moment | Wat je moet leveren | Wat het kost als je dat niet kunt |
|---|---|---|
| Een verlengingsgesprek | Een post-voor-post overzicht van wat het maandbedrag koopt | De aanbieding van de concurrent is uitgesplitst, want die is gaan tellen |
| Een engineer neemt ontslag | Alles wat hij wist, opgeschreven | Zes maanden uitzoeken, één ticket per keer |
| Een klant vertrekt | Een beschrijving van zijn eigen bestand | Drie weken om die samen te stellen, en een referentie die hij eerlijk zal geven |
| Een auditor stelt een scopevraag | Welke systemen persoonsgegevens bevatten en waar die fysiek staan | Een toezichthouder iets vertellen wat later niet waar blijkt |
| Een migratie moet geprijsd worden | Een telling van wat er echt staat | Je biedt op een schatting en eet het verschil op |
Niets daarvan is exotisch. Dat is dinsdag.
Wat je eruit haalt is geen documentatie. Documentatie is iets wat je schrijft en daarna niet meer onderhoudt. Wat je krijgt is een database die weigert een tegenspraak te bevatten, en die vragen beantwoordt die niemand vooraf had bedacht. Verderop in dit stuk staat een kast die 28,6 procent vol blijkt te zitten met apparatuur en 90,7 procent met stroom. Niemand was dat gaan zoeken. Het viel eruit omdat er één keer een wattage op een apparaattype is ingevuld.
De eerlijke kanttekening hoort erbij, en de laatste sectie gaat daarover. Een record is alleen waard wat het je kost om het juist te houden. Maar het alternatief is een bedrijf dat zichzelf niet kan beschrijven, en de eerste die dat ontdekt is meestal een klant.
De andere: Nautobot
Hier loop je binnen ongeveer een week zoeken tegenaan, dus het is nuttig te weten wat er is gebeurd.
In 2021 forkte Network to Code NetBox en noemde het resultaat Nautobot. Geen zachte fork en geen distributie: een harde fork die al vijf jaar uiteenloopt. De eigen v1.0-releasenotes beschrijven het als “a divergent fork of NetBox 2.10”, de repository is aangemaakt op 19 februari 2021 en v1.0.0 kwam op 26 april 2021.3
De genoemde redenen staan op hun eigen blog en zijn in hun woorden beter te lezen dan in de mijne. Drie dingen dreven het. Ze wilden enterprise-support verkopen: “We need to offer high-touch support models with Service Level Agreements (SLAs) we can guarantee. We need flexibility to offer Long-term Support (LTS) for customers who can’t upgrade at the pace of a fast moving open source project.” Ze wilden dat de source of truth in het midden van een automatiseringsplatform zat in plaats van documentatie te dienen. En “there became a growing divergence in our vision about what a Source of Truth for networking should look like and how to get there”.4
Bij die eerste reden moet de datum, want hij is niet meer waar. In februari 2021 was er geen bedrijf achter NetBox dat je iets kon verkopen. NetBox Labs is pas in 2023 opgericht, als spin-out uit NS1 nadat IBM het had overgenomen, mede-opgericht door NetBox’ eigen lead maintainer.5
En het is geen derde partij die een bedrijf op het project van iemand anders heeft gebouwd. NetBox Labs is de beheerder van NetBox: de documentatie van het project zelf zegt “the open source project is stewarded by NetBox Labs and a team of volunteer maintainers”.1 Ze verkopen NetBox Enterprise voor zelfbeheerde installaties, hosten het voor je als NetBox Cloud, en bieden support rond de klok.6
“you cannot buy support for NetBox” was dus een eerlijke uitspraak toen Network to Code forkte, en is nu geen eerlijke uitspraak meer. Achter beide projecten staat een commercieel bedrijf dat iets ondertekent, en in het geval van NetBox is dat bedrijf degene die het project beheert.
De regel die de meeste mensen missen is de volgende, en die is de reden dat dit geen vies verhaal is: “the NetBox project team suggested that we should consider forking.”
Beschaafder wordt een fork nauwelijks. Twee groepen wilden iets anders, zeiden dat over een lange periode, en gingen uit elkaar in plaats van te vechten over één codebase. Beide helften zijn nog steeds Apache 2.0. Niemand heeft iets meegenomen waar hij geen recht op had.
Waar de fork echt voor was
De releasenotes van Nautobot 1.0 noemen wat het toevoegde ten opzichte van NetBox 2.10, en die lijst vertelt het argument beter dan welk blogstuk ook:3
| Wat Nautobot in 2021 toevoegde | Waar NetBox nu staat |
|---|---|
| GraphQL-ondersteuning | NetBox heeft het |
| Git-integratie als databron | NetBox heeft het, als gesynchroniseerde databronnen |
| Single sign-on | NetBox heeft het |
| Secrets | NetBox heeft het via een plugin |
| Scripts en reports samengevoegd tot Jobs | NetBox haalt scripts in 4.7 naar een plugin |
| Custom fields op alle modellen | NetBox heeft brede ondersteuning voor custom fields |
| Data-validation-plugin-API | NetBox heeft custom validation rules |
| Aanpasbare statussen, als databaseobjecten | NetBox’ statussen zijn nog steeds een Python choice set |
| Door gebruikers gedefinieerde relaties tussen modellen | NetBox heeft geen equivalent |
| UUID-primaire sleutels | NetBox gebruikt integer-sleutels |
| Verbeteringen aan de plugin-API | NetBox’ plugin-framework is sindsdien flink gegroeid |
De bovenste helft is grotendeels naar elkaar toe gegroeid. Meerdere dingen die Nautobot in 2021 uitbracht, kwamen daarna in NetBox terecht, en dat is wat meestal gebeurt als twee projecten dezelfde problemen in het openbaar oplossen.
De onderste drie zijn niet naar elkaar toe gegroeid, en ze zijn architectonisch in plaats van cosmetisch. Ik heb vandaag beide codebases nagekeken in plaats van op de aantekeningen uit 2021 te vertrouwen.
Nautobots Status is een databasemodel, in de eigen broncode beschreven als een “Model for database-backend enum choice objects”, dus een status is een regel die iemand in de UI kan toevoegen. NetBox’ statussen komen uit een Python choice set, en daarom voegt de sectie verderop er een toe door configuration.py aan te passen en te herstarten. Nautobot heeft Relationship- en RelationshipAssociation-modellen, dus je kunt een relatie tussen twee bestaande objecttypes definiëren zonder code te schrijven. NetBox’ antwoord op dat probleem is een plugin, en dat is de laatste reeks secties in dit stuk. En Nautobots primaire sleutels zijn UUID’s, waar die van NetBox integers zijn.
Ze hebben ook gedaan waarvoor ze forkten. Vandaag publiceert Nautobot 3.2.x en 2.4.x op dezelfde dag, wat een echte langetermijnonderhoudslijn naast de huidige is, en dat was een van de drie genoemde redenen.
Welke van de twee
Eerst de eerlijke cijfers. NetBox heeft 21.625 sterren en 3.133 forks; Nautobot heeft 1.617 en 422.7 Naar beide is in de laatste twee dagen gepusht, beide zijn Apache 2.0, en achter beide staat nu een commercieel bedrijf dat support en hosting verkoopt.
Dat gat is geen oordeel over kwaliteit. Het weerspiegelt vijf jaar voorsprong en het feit dat de meeste mensen die een source of truth nodig hebben NetBox het eerst vinden. Maar het beslist wel over wat meestal meer uitmaakt dan features: hoeveel plugins, integraties, Ansible-modules, forumantwoorden en collega’s je vindt voor degene die je kiest.
Dus: wil je een inventaris en een source of truth die andere systemen lezen, en wil je het grootste ecosysteem en het makkelijkste aannemen, dan is het antwoord NetBox, en daar gaat de rest van dit stuk over. Is jouw reden voor een source of truth specifiek om er automatisering uit te besturen, of wil je statussen en relaties laten definiëren door je team in plaats van door een configuratiebestand en een herstart, ga dan eerst goed naar Nautobot kijken voordat je beslist.
Laat niemand je er een van de twee verkopen op support alleen. Beide kampen hebben dat nu gedekt, en de verschillen die er over vijf jaar nog zijn, zijn die uit de tabel hierboven.
Wat je niet moet doen is er een kiezen omdat iemand je heeft verteld dat de ander dood is. Geen van beide is dat, en beide brachten deze week nog releases uit.
Er een aan de praat krijgen
Twee routes, en ik heb ze hier beide gelopen. Containers als je het in twintig minuten werkend wilt hebben, pakketten op een host als het dragend wordt.
De container-stack
De community onderhoudt netbox-docker, en dat is het snelle antwoord:8
git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
tee docker-compose.override.yml <<'EOF'
services:
netbox:
ports:
- 8000:8080
EOF
docker compose pull
docker compose up
Dat geeft je de applicatie, PostgreSQL, Redis en een achtergrondworker, aan elkaar geknoopt. Ik heb hetzelfde met de hand onder podman gebouwd om de onderdelen te zien, en de onderdelen zijn het waard om te kennen, want twee ervan pakken mensen:
| Container | Doet wat | Als je hem weglaat |
|---|---|---|
netbox | De Django-applicatie achter gunicorn | niets werkt |
postgres | De database, 15 of nieuwer | niets werkt |
redis / valkey | Twee databases: één voor taken, één voor caching | niets werkt |
netbox-worker | rqworker, die de takenwachtrij leegwerkt | webhooks vuren nooit, achtergrondtaken lopen nooit, en niets waarschuwt je |
netbox-housekeeping | Het periodieke opruimen | changelog-records verlopen nooit |
Die vierde regel is de belangrijke. Zonder worker ziet alles er gezond uit. Event rules stapelen op en blijven liggen.
Twee dingen beten me bij de eerste run, en geen van beide staat in een foutmelding waar je op zou zoeken.
De eerste migratie duurt lang. Geen minuut. Op deze machine waren het er meerdere, omdat NetBox 4.7 django-mptt vervangt door PostgreSQL ltree en onderweg elke hiërarchische tabel herbouwt. De container zit daar gewoon migraties toe te passen. Laat hem met rust.
Zonder API_TOKEN_PEPPERS kun je geen v2-API-token aanmaken, en het enige teken is een waarschuwing in het log:
UserWarning: API_TOKEN_PEPPERS is not defined. v2 API tokens cannot be used.
Zet er minstens één, van minstens vijftig tekens, voordat je gaat zoeken waarom de API je afwijst.
Op een host, uit de pakketten
De gedocumenteerde route is getest op Ubuntu 24.04. Ik heb hem van begin tot eind op een schone gelopen, en het kwam hierop uit:
| Component | Wat 24.04 me gaf | Wat NetBox 4.7 nodig heeft |
|---|---|---|
| PostgreSQL | 16.15 | 15 of nieuwer |
| Redis | 7.0.15 | 6.0 of nieuwer |
| Python | 3.12.3 | 3.12, 3.13 of 3.14 |
| Django | 6.1.1 | komt met NetBox |
| NetBox | v4.7.2 |
Die minima zijn die van NetBox zelf, en 4.7 heeft zowel de PostgreSQL- als de Redis-ondergrens verhoogd.9
De hele klus is vijf stappen.
# 1. the services
sudo apt install -y postgresql redis-server
sudo -u postgres psql -c "CREATE DATABASE netbox;"
sudo -u postgres psql -c "CREATE USER netbox WITH PASSWORD 'something-you-generated';"
sudo -u postgres psql -c "ALTER DATABASE netbox OWNER TO netbox;"
sudo -u postgres psql -d netbox -c "GRANT CREATE ON SCHEMA public TO netbox;"
# 2. the build dependencies
sudo apt install -y python3 python3-pip python3-venv python3-dev build-essential libxml2-dev libxslt1-dev libffi-dev libpq-dev libssl-dev zlib1g-dev git
# 3. the application, at a release tag rather than at main
sudo mkdir -p /opt/netbox && cd /opt/netbox
sudo git clone https://github.com/netbox-community/netbox.git .
sudo git checkout v4.7.2
sudo adduser --system --group netbox
sudo chown -R netbox /opt/netbox/netbox/media/ /opt/netbox/netbox/scripts/ /opt/netbox/netbox/reports/
# 4. the configuration: five values, no more
cd /opt/netbox/netbox/netbox/
sudo cp configuration_example.py configuration.py
python3 /opt/netbox/netbox/generate_secret_key.py # run it twice
sudo $EDITOR configuration.py
# 5. let the upgrade script do the rest
sudo /opt/netbox/upgrade.sh
Die vijf waarden zijn ALLOWED_HOSTS, DATABASES, REDIS, SECRET_KEY en API_TOKEN_PEPPERS. Genereer de laatste twee apart en gebruik niet de een voor de ander. Laat de sleutelgenerator dus twee keer lopen en plak de resultaten op verschillende regels.
upgrade.sh bouwt de virtuele omgeving, installeert elke Python-afhankelijkheid, voert de migraties uit, bouwt de documentatie voor offline gebruik en verzamelt de statische bestanden. Als het op een verse machine klaar is, print het een waarschuwing die alarmerend lijkt en dat niet is:
WARNING: No existing virtual environment was detected. A new one has
been created. Update your systemd service files to reflect the new
Python and gunicorn executables. (If this is a new installation,
this warning can be ignored.)
Dan een superuser, en dat loopt zo:
source /opt/netbox/venv/bin/activate
cd /opt/netbox/netbox && python3 manage.py createsuperuser
Voor iets echts zet je gunicorn ervoor in plaats van runserver. De configuratie en de unit-bestanden staan al in de repository, en dat is het detail dat je moet kennen omdat mensen hun eigen schrijven:
sudo cp /opt/netbox/contrib/gunicorn.py /opt/netbox/gunicorn.py
sudo cp -v /opt/netbox/contrib/*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now netbox netbox-rq
netbox.service draait gunicorn op 127.0.0.1:8001, netbox-rq.service draait de worker, en nginx of Apache gaat ervoor om TLS af te handelen en /static te serveren. Twee diensten, en de tweede is dezelfde worker die de container-stack nodig heeft.
De volgorde waarin je het moet vullen
Een verse NetBox is een lege database met meningen, en het eerste uur ermee gaat meestal op aan uitzoeken welke dat zijn. Je gaat een apparaat toevoegen, en het laat je niet.

Die rode sterretjes zijn de hele les. NetBox legt niets vast voordat de dingen bestaan waar het aan hangt, en het doet niet moeilijk: een apparaat zonder type is een regel die geen van de vragen kan beantwoorden waar een apparaat voor is.
In plaats van te gokken heb ik dus het model gevraagd welke foreign keys echt verplicht zijn, en daarna geprobeerd elke regel via de API te breken om te zien wat er terugkomt.
POST /api/dcim/device-types/ no manufacturer
{"manufacturer": ["This field is required."]}
POST /api/dcim/devices/ no device type
{"device_type": ["This field is required."]}
POST /api/dcim/interfaces/ no device
{"device": ["This field is required."]}
POST /api/ipam/aggregates/ no RIR
{"rir": ["This field is required."]}
POST /api/circuits/circuits/ no provider, no type
{"provider": ["This field is required."], "type": ["This field is required."]}
POST /api/virtualization/virtual-machines/ nothing at all
{"__all__": ["A virtual machine must be assigned to a site, cluster, or device."]}
Elk ervan weigerde, en zei precies wat er ontbrak. Hier dezelfde informatie als lijst van voorwaarden:
| Om aan te maken | Eerst nodig | Optioneel, maar je wilt het eerst |
|---|---|---|
| Tenant | niets | een tenantgroep |
| Region, Site group | niets | een ouder van dezelfde soort, ze nesten |
| Site | niets | een Region, een Site group, een Tenant |
| Location | een Site | een bovenliggende Location, een Tenant |
| Rack type | een Manufacturer | |
| Rack | een Site | een Location, Rack group, Rack role, Rack type, Tenant |
| Device type | een Manufacturer | |
| Device role | niets | een bovenliggende Device role, ze nesten |
| Device | een Device role, een Device type, een Site | een rack en positie, een Platform, een Tenant |
| Interface, en elk ander component | een Device | |
| Cable | twee dingen om op af te sluiten | een Tenant |
| Aggregate | een RIR | een Tenant |
| VLAN | niets | een VLAN group, een Role, een Tenant |
| Prefix | niets | een Site, een VLAN, een Role, een VRF, een Tenant |
| IP address | niets | een Interface om het aan toe te wijzen, een Tenant |
| Circuit | een Provider en een Circuit type | een Tenant |
| Circuit termination | een Circuit | een Site om op te landen |
| Cluster | een Cluster type | een Cluster group, een Site, een Tenant |
| Virtual machine | een Site, een Cluster of een Device | een Platform, een Tenant |
| VM interface | een Virtual machine |
De tweede kolom is die wat je kost. Prefixes, IP-adressen, VLAN’s en tenants vereisen helemaal niets, dus niets houdt je tegen om ze op dag één in elke volgorde aan te maken. Of ze ergens goed voor zijn is een andere vraag, want een adres zonder interface erachter is een regel in een lijst, en een apparaat zonder tenant is een apparaat dat je later nog eens gaat bewerken.
De volgorde om echt in te werken
Dat geeft een reeks. Werk hem af en niets weigert je ooit iets.
Eerst de dingen waar al het andere aan hangt. Niets hiervan is opwindend en alles ervan is goedkoop om fout te doen.
- Tenantgroepen, dan tenants. Doe deze voor al het andere. Achtentwintig modellen nemen een tenant, waaronder Site, Location en Rack, dus als de klanten nog niet bestaan kun je ze niet onderweg meestempelen en ga je later in bulk bewerken.
- Regio’s en site groups. Beide optioneel, beide nesten in zichzelf, en ze staan los van elkaar. Regio’s zijn voor geografie, site groups voor functie, en je kunt er een, beide of geen gebruiken.
- Sites. De wortel van bijna alles. Een site vereist niets, en daarom is het het eerste wat je echt kunt aanmaken.
- Locaties. Die hebben een site nodig, en ze nesten, dus een hal met rijen met pods is één model drie diep.
- Rack roles en rack groups. Beide optioneel. Rack groups zijn vlak en staan naast locaties als tweede as, wat handig is voor rijen en pods.
- Manufacturers, dan rack types. Een rack type heeft een manufacturer nodig. Laat rack types weg als je de kasten zelf niet modelleert.
- Racks. Die hebben een site nodig. Geef je er ook een locatie bij, dan moet die locatie bij die site horen, en NetBox controleert dat.
Dan de hardwarecatalogus, niet de hardware. Voordat je één apparaat kunt toevoegen heb je manufacturers, device types, device roles en, in de praktijk, platforms nodig.
- Manufacturers. Je hebt er misschien al een paar uit stap 6, want rack types hebben ze ook nodig. Module types eveneens.
- Device types, die elk een manufacturer nodig hebben.
- Device roles, die nesten, en platforms.
Dit is de stap die mensen overslaan, en het is de stap die bepaalt hoeveel typewerk de rest van de klus kost. Een device type draagt zijn eigen interfaces, poorten en bays als templates, dus elk apparaat dat je ervan aanmaakt komt met de juiste componenten er al op. Doe het type één keer goed en veertig ervan inracken zijn veertig namen.
Platform is de buitenbeen in die lijst, want een apparaat vereist er strikt genomen geen. Doe het nu toch. Het is wat later het config-template en de NAPALM-driver draagt, en teruggaan om het op een heel bestand te zetten is dezelfde avond die je aan tenants had besteed.
Dan de apparatuur zelf.
- Apparaten. Rol, type en site zijn alle drie verplicht. Rack en positie zijn optioneel, en als je ze geeft moeten ze consistent zijn met de site.
- Componenten, als het device type ze niet al heeft geleverd.
- Kabels, tussen de componenten.
Dan de adressering, want een adres wil een interface om op te leven en de interface bestaat pas na stap 12.
- RIR’s, dan aggregates.
- Prefix- en VLAN-rollen, VRF’s, VLAN groups en dan VLAN’s.
- Prefixes, dan de losse IP-adressen.
Dan de commerciële laag.
- Providers, provider accounts en circuit types.
- Circuits, en sluit ze dan af op sites.
En het virtuele bestand, dat het fysieke spiegelt.
- Cluster types en cluster groups, dan clusters.
- Virtuele machines, die een site, een cluster of een apparaat nodig hebben, en dan hun interfaces.
Stap 1 tot 10 zijn een middag en voelen als administratie. Er is niets glamoureus aan. Het is ook de middag die bepaalt of stap 11 een ochtend of veertien dagen kost.
De regels die later bijten
Verplichte velden zijn de makkelijke helft, want die falen direct en vertellen je waarom. Waar mensen op stuklopen zijn de consistentieregels, en die vuren pas als je genoeg data hebt om jezelf tegen te spreken:
device at Leeds, put in a Manchester rack
{"rack": ["Rack MCR1-A07 (A07) does not belong to site Leeds Edge."]}
device at Leeds, in a Manchester location
{"location": ["Location Hall 2 does not belong to site Leeds Edge."]}
rack at Leeds, in a Manchester location
{"__all__": ["Assigned location must belong to parent site (Leeds Edge)."]}
a second device in a unit that is already taken
{"position": ["U39.0 is already occupied or does not have sufficient
space to accommodate this device type: MX204 (1.0U)"]}
Die laatste is degene waar ik naar zou wijzen als iemand vroeg waarom je je hier druk om maakt. NetBox weet dat het apparaat 1U is, weet wat er in de kast staat, en laat je geen twee dingen op dezelfde plek vastleggen. Jouw spreadsheet laat je dat de hele middag doen zonder een woord te zeggen, en je komt het erachter als iemand met een doos in zijn handen in de hal staat.
Niets hiervan is instelbaar en niets hiervan zou dat moeten zijn. Dat is het verschil tussen een record en een wens.
In gebruik: wat elk scherm je geeft
Met het bestand geladen is dit wat je er echt uit terugkrijgt. Alles hieronder is diezelfde instantie: één kast, veertien apparaten, gekabeld en geadresseerd over drie klanten.
Een apparaat is zijn componenten
Klik in een van die hypervisors en het interessante tabblad is niet de samenvatting, het zijn de interfaces.

Lees één regel dwars. De interface, zijn snelheid, wat je erover hebt geschreven, het adres erop, het label op de kabel, en de poort aan de andere kant. Dat is één query, en het is het antwoord op de vraag die iedereen echt stelt, namelijk “wat hangt hier aan”.
Let op waar het adres zit. Het zit op eno1, niet op de server. Dat klinkt als muggenziften, precies tot je een kast hebt met een managementinterface, twee data-interfaces en een loopback, en iemand vraagt welk adres ervoor antwoordt. Een model dat adressen aan apparaten hangt kan het je niet vertellen. Dit kan het wel, en het kan ook het volkomen gewone geval van vier adressen op één interface bevatten.
De kabel volgen
Kabels sluiten af op componenten, niet op apparaten. Een interface aan de ene kant, een interface aan de andere, of een front port, een rear port, een stroomuitgang, een circuitafsluiting. Dat is het detail waarop de hele functie rust, en het is waarom de interfacelijst hierboven de andere kant in een eigen kolom kon afdrukken zonder dat het haar is verteld.
Modelleer een kabel van apparaat naar apparaat en je hebt een plaatje getekend. Sluit hem af op de poorten en NetBox kan hem aflopen.

Hier één hop, want het is een direct-attach-kabel. Zet patchpanelen in het midden en het loopt ze af, paneel voor paneel, en vertelt je wat er aan het eind van een traject door drie kasten hangt. Dat is de klus waar anders een zaklamp en iemand die het andere eind van een toongenerator vasthoudt aan te pas komt.
Het spoor drukt ook de volledige locatie van elk uiteinde af. Site, hal, kast, kant, rack-unit. Als je ooit aan de telefoon hebt geprobeerd een remote-hands-engineer uit te leggen naar welke kast hij moet kijken, is die regel de hele waarde.
Adressen, als boom in plaats van als tabblad
IPAM is de helft waarvoor mensen komen.

De inspringing wordt uit de adressen zelf berekend. Je vertelt NetBox niet dat 10.20.20.0/24 in 10.20.0.0/16 zit, het rekent dat uit, en het blijft dat uitrekenen als iemand er volgend jaar een /26 middenin zet.
Elke regel draagt de dingen waarop je echt filtert: het VLAN waar hij naar wijst, de rol, en de klant van wie hij is. De benutting wordt ook berekend.
Open er een en je krijgt de adressen erin, en de gaten.

Die groene regels zijn de vrije ruimte, in lijn met de gebruikte ruimte getoond. Er is een API-aanroep die je het volgende vrije adres uit een prefix teruggeeft, en dat is het ene stuk IPAM-automatisering dat zich direct terugbetaalt, want het is wat mensen anders doen door naar een spreadsheet te turen en te hopen.
Een interface neemt zoveel adressen als je wilt, uit beide families, en je benoemt er een van elk als het primaire van het apparaat.

Twee IPv4 en twee IPv6 op één poort, wat een gewone dinsdag is en iets wat een apparaatgericht model helemaal niet kan uitdrukken.
Voer adressen in met het masker van het netwerk waarop ze liggen, niet als /32. NetBox accepteert 10.20.20.11/32 en zet het onder het juiste prefix, want de insluiting wordt uit het hostadres bepaald. Wat het niet doet is je achteraf overrulen: het masker wordt precies zo opgeslagen als het is getypt en zo teruggegeven aan alles wat het leest, dus een /32 op een LAN-adres rendert een /32 in de apparaatconfiguratie. De plek waar dat bijt is drie maanden later in een config-template, niet vandaag in het formulier.
Eén installatie, drie klanten
De meeste objecten in NetBox kunnen aan een tenant worden toegewezen. Een onderneming gebruikt dat voor bedrijfsonderdelen. Verkoop je managed services, dan maak je er een per klant.

Dat paneel rechts is het antwoord op “wat heeft deze klant”, en het heeft zichzelf samengesteld. Geen rapport, geen spreadsheet, geen navraag bij de engineer die het heeft gebouwd.
Het is de moeite om precies te zijn over wat tenancy betekent, want het op dag één fout doen is een jaar ontwarren later. Een tenant betekent dat het object aan die klant is toegewijd. Een router die alleen hem bedient krijgt zijn tenant. Een firewall die vier van hen bedient hoort bij geen van hen, dus die krijgt er geen, en de relatie gaat ergens anders heen. Daarover verderop meer, want dat is het punt waar de meeste mensen merken dat ze iets van zichzelf moeten toevoegen.
Zet de cijfers op het apparaattype
Dit is de stap die een inventaris scheidt van iets wat vragen beantwoordt, en hij kost ongeveer tien minuten per apparaattype.
Een device type kan zijn gewicht dragen en, via zijn power port-templates, zijn stroomafname. Zet die één keer op het type en elk apparaat dat je er ooit van aanmaakt erft ze. Laat ze weg en NetBox vertelt je vrolijk dat een kast 28,6% vol zit en niets anders.

Ik heb alle vijf types hier een gewicht gegeven, elk twee power port-templates met een maximale en een toegewezen afname, het PDU-type een inlet en twaalf uitgangen, daarna een power panel en twee feeds naar de kast aangemaakt en het geheel gekabeld: elke PSU1 van elk apparaat naar PDU A, elke PSU2 naar PDU B, en de inlet van elke PDU naar zijn feed.

Dan verandert de rackpagina compleet.

Ruimtebenutting 28,6%. Strombenutting 90,7%.
Die kast zit voor een derde vol met apparatuur en is bijna door zijn stroom heen, en dat is een feit over je bestand dat geen spreadsheet ooit uit zichzelf aandraagt. Het is ook het feit dat bepaalt of de volgende order daar wordt ingeracked of ergens anders, en het viel uit data die je één keer hebt ingevoerd, op de types.
Twee dingen die het op nul laten staan
Ik had eerst 0,0%, twee keer, en beide oorzaken zijn het waard om te kennen want geen van beide levert een fout op.
De uitgangen moeten naar de inlet verwijzen. Een stroomuitgang op een PDU heeft een veld power_port dat naar de bovenliggende poort op hetzelfde apparaat wijst. Laat het leeg en de keten is verbroken: NetBox heeft geen manier om te weten dat die twaalf uitgangen door die inlet worden gevoed, dus niets aggregeert.
Laat de afnamevelden van de inlet leeg. Dit is de contra-intuïtieve. NetBox berekent de afname van een power port uit wat eraan hangt alleen als zijn eigen twee afnamevelden leeg zijn:
if self.allocated_draw is None and self.maximum_draw is None:
...aggregate the downstream power ports...
# otherwise
return {'allocated': self.allocated_draw or 0, ...}
Ik had behulpzaam maximum_draw: 7400 op de PDU-inlet gezet, want daar is de PDU op gespecificeerd. NetBox geloofde me dus, nam allocated_draw als niet gezet, en rapporteerde nul. Maak beide leeg en het rekent het uit:
mcr1-pdu-a INPUT -> allocated 5340 VA, maximum 8480 VA, across 12 outlets
mcr1-pdu-b INPUT -> allocated 5340 VA, maximum 8480 VA, across 12 outlets
feed MCR1-A07-A: available 5888 VA (230 V x 32 A x 80% max utilisation)
RACK power utilisation: 90.7 %
RACK weight: 166.6 kg of 900 kg
De regel is dus: zet echte cijfers op de bladeren, en laat de tussenliggende poorten leeg zodat NetBox ze kan optellen. Een administratief ingestelde waarde wint altijd van de berekende, wat correct gedrag is en precies het verkeerde om op een PDU te doen.
Koeling, wat nieuw is
Versie 4.7 heeft koeling aan DCIM toegevoegd, en het kwam met de helft die duur wordt. Een rack draagt een cooling capability van lucht, hybride of vloeistof en een capaciteit in kilowatt; een device type draagt een cooling method. Daarboven zitten cooling sources voor de koelmachines en CRAC-units, cooling feeds die een lus naar een rack voorstellen, en intake- en outflow-componenten op de apparaten zelf voor koudeplaten en verdelers.
De kast hierboven leest Hybrid, 15,00 kW, omdat ik dat aan het rack heb verteld. Neem je dit jaar vloeistofgekoelde apparatuur in ontvangst, dan is dat een model voor wat je nu in een spreadsheet bijhoudt.
Eén installatie, veel klanten
Tenancy is de reden dat een provider één NetBox kan draaien in plaats van een per klant, en het is de moeite te begrijpen wat het doet en, belangrijker, wat het niet doet.
Achtentwintig van NetBox’ modellen dragen een tenant-veld. Sites, locaties, racks en rackreserveringen. Apparaten, kabels en virtual device contexts. Prefixes, IP-adressen, ranges, aggregates, VLAN’s, VLAN groups, VRF’s, route targets, ASN’s. Circuits en circuit groups. Clusters en virtuele machines. Tunnels, L2VPN’s, wireless LAN’s en links. Power feeds en cooling feeds.
Dat is elk factureerbaar zelfstandig naamwoord. Zet het consequent en “wat heeft deze klant” is geen onderzoek meer.
Maar een tenant is een label, geen slot. Het zegt dat het object aan die klant is toegewijd. Het houdt niemand die kan inloggen tegen om het geheel te lezen, wat binnen één bedrijf prima is en helemaal niets meer waard is zodra een klant een account heeft.
Er volgen twee dingen uit, en het tweede is wat mensen fout doen.
Een tenant betekent toegewijd. Een router die maar één klant bedient krijgt zijn tenant. Een firewall die er vier bedient hoort bij geen van hen, dus die krijgt niets, en je legt de relatie vast op wat je werkelijk hebt verkocht. Een tenant op gedeelde apparatuur forceren maakt elk rapport dat op tenancy is gebouwd stilletjes verkeerd.
En toegang is een compleet eigen mechanisme.
In de permissions zit de isolatie
NetBox’ object permissions nemen een JSON-beperking, en de beperking is een Django-ORM-filter. Hij versmalt de queryset voordat er iets uit wordt gebouwd, dus elke weergave, export, API-aanroep en zoekopdracht wordt ermee versmald.

Drie velden doen het werk. De objecttypes waarvoor hij geldt, de acties die hij toekent, en die beperking onderaan. Al het andere is boekhouding.
Hier is de apparatenlijst als beheerder.

En hier is dezelfde URL, dezelfde installatie, ingelogd als de klant.

Twee regels in plaats van veertien, en kijk naar het menu links. Het is ingeklapt tot de vier dingen die dat account mag aanraken. Niemand heeft dat ingesteld. De navigatie wordt uit dezelfde permissions gebouwd, dus een klant ziet nooit een link naar iets wat hem zou weigeren.
De twee manieren waarop het nee zegt
Dit is het detail dat je moet kennen, want de twee weigeringen betekenen iets anders en beide zijn bedoeld.
| Het token van de klant vraagt om | Het krijgt |
|---|---|
/api/dcim/devices/ | 200, één regel van twee |
/api/dcim/devices/1/, zijn eigen | 200 |
/api/dcim/devices/2/, dat van iemand anders | 404 |
/api/tenancy/tenants/ | 403 |
/api/dcim/sites/, nooit toegekend | 403 |
| helemaal geen token | 403 |
404 betekent dat het type het jouwe is maar die regel niet. De beperking heeft hem uit de queryset gehaald, dus voor het verzoek bestaat hij niet. Een 403 daar zou bevestigen dat hij bestaat, en een nieuwsgierige klant toestaan je bestand te tellen door de ID’s af te lopen.
403 betekent dat het type nooit het jouwe was. Tenants en sites zijn nooit toegekend, dus die weigeren meteen, en de klant kan niet opsommen wie er nog op het platform zit.
Laat ze dan schrijven
Alleen-lezen is het makkelijke geval. De echte vraag is of je een klant met bewerkrechten die rechten kunt toevertrouwen, dus heb ik change onder dezelfde beperking toegekend en ben ik de uitweg gaan zoeken.
| Poging | Resultaat |
|---|---|
| Zijn eigen apparaat bewerken | 200, opgeslagen |
| Het apparaat van een andere klant bewerken | 404 |
| Zijn eigen apparaat bewerken en naar de tenant van de andere klant verplaatsen | 403 |
| Zijn eigen apparaat verwijderen | 403, delete is nooit toegekend |
De derde regel is de belangrijke. Je eigen apparaat aan de tenant van iemand anders toewijzen is de voor de hand liggende ontsnapping, want het object zit bij aankomst van het verzoek binnen je beperking en erna erbuiten. NetBox beoordeelt de beperking tegen de toestand waarin het object zou achterblijven, dus het weigert. Ik heb het apparaat teruggelezen in plaats van op de statuscode te vertrouwen, en de tenant was niet verschoven.
Dat is het gat dat zelfgebouwde multi-tenancy meestal open laat, en het wordt normaal gevonden door een klant en niet door een test.
Variabelen die moeten overerven
Hier is het onderscheid dat mensen fout doen, en het goed doen scheelt veel bewerken.
Een custom field is een waarde op één object. Je zet hem per object, en daar blijft hij. Goed voor een feit over het ding zelf: een asset tag, een supportcontractnummer, een ingebruiknamedatum.
Een config context is een waarde die aan een eigenschap hangt, en die alles wat op die eigenschap past overerft. Goed voor een variabele die moet doorsijpelen: je NTP-servers, je syslog-bestemmingen, je SNMP-community, je DNS-domein, je management-VLAN, je backupvenster.
Betrap je jezelf erop hetzelfde custom field op veertig apparaten op dezelfde waarde te zetten, dan wilde je een config context.

Een context is willekeurige JSON, en hij kan aan een regio, site group, site, locatie, device type, rol, platform, cluster, cluster type, cluster group, tenantgroep, tenant of tag worden gehangen. Tenant staat in die lijst, en voor een provider betekent dat dat een feit dat overal voor één klant geldt hem volgt naar elk apparaat dat je ooit voor hem toevoegt.
Het samenvoegen gaat per sleutel, en het gewicht bepaalt wie elke afzonderlijke wint.

Lees het rechterpaneel tegen het linker. De regio levert vier sleutels op gewicht 1000. De site levert één sleutel op gewicht 2000. De gerenderde context houdt het domein, de NTP-servers en de community van de regio onaangeroerd, en neemt de syslog-server van de site, want dat is de enige sleutel waar iets om heeft gestreden.
Je schrijft de uitzondering, niet een verse kopie van alles met de uitzondering erin. Dat is de hele waarde, en het is waarom dit schaalt waar een variabelenbestand per apparaat dat niet doet.
Lokale context wint van alles erboven
De overervingsstapel heeft een top, en dat is het object zelf. Local context data op een apparaat wint van elke source context die erop van toepassing is, wat de gewichten ook zijn.
Ik heb dit op mcr1-core-01 gezet:
{"syslog_servers": ["10.20.10.99"], "note": "this box logs somewhere else"}
en zijn gerenderde context werd:
{
"note": "this box logs somewhere else",
"domain": "mcr1.example.net",
"ntp_servers": ["172.16.10.22", "172.16.10.33"],
"snmp_community": "n0rthwest",
"syslog_servers": ["10.20.10.99"]
}

De lokale syslog-server versloeg zowel de site-override op gewicht 2000 als de regio op gewicht 1000. Alles waarover hij niets zei is alsnog overgeërfd, dus het domein, de NTP-servers en de community kwamen onaangeroerd door, en de nieuwe sleutel werd simpelweg toegevoegd.
Dat is de nooduitgang voor die ene kast die echt anders is, en het paneel vertelt je ronduit dat het alle source contexts overschrijft. Betrap je jezelf erop het op veel apparaten te gebruiken in plaats van op één, dan heb je een eigenschap gevonden die die apparaten delen en wilde je een context die daarop is afgebakend.
Drie valkuilen
Een context zonder afbakening is globaal. Maak er een en vergeet hem aan iets toe te wijzen, en hij geldt voor elk apparaat en elke virtuele machine die je hebt. Dat is gedocumenteerd gedrag en soms wat je wilt. Het is ook geruisloos.
Sleutels mogen geen streepjes hebben als je ze eenvoudig wilt bereiken. Contextdata is JSON, dus ntp-servers is volkomen legaal, maar Jinja-variabelenamen zijn geen JSON-sleutels. {{ ntp-servers }} wordt als aftrekking geparseerd en werpt UndefinedError: 'ntp' is undefined. Schrijf {{ ntp_servers }} tegen data met streepjes en je krijgt een lege string, HTTP 200, en nergens een waarschuwing. Gebruik underscores in de sleutels en het voor de hand liggende template werkt.
Een profiel kan de typefouten stoppen. Een config context-profiel groepeert verwante contexts en dwingt bij het opslaan een JSON-schema op hun data af. Ik heb er een een schema gegeven dat syslog_servers als array van IPv4-strings eist, en daarna de twee fouten gemaakt die mensen echt maken:
{"syslog-server": ["10.1.1.1"]} singular, by accident
400 Data does not conform to profile schema:
'syslog-servers' is a required property
{"syslog_servers": [...], "syslog_port": 99999}
400 Data does not conform to profile schema:
99999 is greater than the maximum of 65535
Geweigerd waar iemand ze maakte, in plaats van vierhonderd apparaten later.
De configuratie renderen
Contextdata plus een Jinja-template geeft je een configuratiebestand. Het apparaat zit in scope als device, zijn samengevoegde context zit in scope als gewone variabelen, en je kunt zijn componenten aflopen.

Alles op die pagina kwam ergens anders vandaan, en dat is het punt:
| Regel | Waar hij vandaan kwam |
|---|---|
host-name mcr1-core-01 | van het apparaat |
domain-name mcr1.example.net | van de regionale config context |
location "Manchester DC1 / MCR1-A07 / U39" | van de site, het rack en de positie erin |
server 172.16.10.22 | van de regionale context, gewicht 1000 |
host 10.20.10.99 any notice | van de eigen lokale context van het apparaat, die zowel de site als de regio verslaat |
family inet address 10.20.10.11/24 | van het adres op die interface, met het masker zoals getypt |
Het template wordt opgelost via het apparaat, dan de rol, dan het platform, en het verzoek mislukt als geen van de drie er een heeft. Je wijst dus één keer een template aan een platform toe, en elk apparaat dat die software draait rendert eruit, tenzij zijn rol of het apparaat zelf iets anders zegt. Daar is een platform voor: het besturingssysteem of de softwarefamilie van de fabrikant, niet de hardware.
NetBox rendert. Het pusht niet. De uitvoer op de kast krijgen is de taak van jouw automatisering, en op de dag dat een templatefout anders vierhonderd apparaten had herconfigureerd, ben je blij dat dat twee verschillende systemen zijn.
Plugins
Een plugin is een Django-applicatie die naast NetBox wordt geïnstalleerd. Hij kan modellen toevoegen, pagina’s toevoegen, beide API’s uitbreiden, inhoud in bestaande templates injecteren, navigatie toevoegen, achtergrondtakenwachtrijen toevoegen en verdere Django-apps laden. Er is heel weinig wat hij niet kan, want eronder is het gewoon Django.
Er een installeren is vier commando’s en een herstart:
source /opt/netbox/venv/bin/activate
pip install netbox-topology-views netbox-qrcode
# add the package names to PLUGINS in configuration.py, then
python3 manage.py migrate
python3 manage.py collectstatic --no-input
sudo systemctl restart netbox netbox-rq
Op de container-stack gaan dezelfde pakketten in plugin_requirements.txt en bouw je de image opnieuw. In beide gevallen: pin de versies en controleer eerst de compatibiliteitsmatrix, want een plugin die een NetBox-release niet heeft bijgehouden weigert de hele applicatie te starten in plaats van zichzelf uit te schakelen.

De gepubliceerde catalogus noemt er 31. Dit zijn de plugins die het waard zijn te kennen, met de licentie waaronder elk werkelijk wordt uitgebracht:
| Plugin | Wat hij doet | Licentie |
|---|---|---|
| DNS | Zones, records en nameservers als source of truth | MIT |
| BGP | Sessies, communities en routing-policies | Apache 2.0 |
| Topology Views | Grafische topologiekaarten, gebouwd uit je kabels | Apache 2.0 |
| Floorplan | Grafische site- en locatiekaarten | LGPL 3.0 |
| QR Code | Codes op racks, apparaten en kabels, voor assetlabels | Apache 2.0 |
| ACLs | Access lists en regels | Apache 2.0 |
| Prometheus SD | Levert Prometheus zijn hostlijst rechtstreeks uit NetBox | MIT |
| Documents | Documenten gekoppeld aan circuits en apparaten | Apache 2.0 |
| Lifecycle | Hardware end of life, licenties en contracten | Apache 2.0 |
| Contract | Contracten en facturen | MIT |
| Reorder Rack | Rack-units verslepen | Apache 2.0 |
| Branching | Geïsoleerde, samenvoegbare branches van je data | NetBox Limited Use |
| Custom Objects | Nieuwe objecttypes, in de UI gedefinieerd | NetBox Limited Use |
Prometheus SD is de eerlijke vorm van het hele idee. NetBox weet wat er bestaat, dus laat NetBox het aan de monitoring vertellen, en stop met het onderhouden van een tweede hostlijst die uit elkaar loopt.
Eén ding om te weten voordat je op de laatste twee regels bouwt. NetBox zelf is Apache 2.0 en is dat sinds DigitalOcean het in 2016 vrijgaf, en het project wordt vandaag beheerd door NetBox Labs samen met een team vrijwillige maintainers.21 NetBox Branching en NetBox Custom Objects zijn dat niet: ze worden uitgebracht onder de NetBox Limited Use License 1.0, die gebruik toestaat “only as part of a NetBox installation obtained from NetBox Labs or a NetBox distributor authorized by NetBox Labs, and only for your own internal use”, en die niet het recht geeft de software te gebruiken “to provide a managed service or software products that includes, integrates with, or extends NetBox in a way that competes with any product or service of NetBox Labs”. Heb je NetBox Community van GitHub geïnstalleerd en draai je het namens klanten, lees dan zelf de voorwaarden voordat je er een schema achter zet.10 NetBox’ eigen installatiehandleiding beveelt beide plugins aan zonder het te vermelden.11
Custom fields
Een custom field voegt een attribuut toe aan een bestaand model. Waarden worden als JSON naast elk object opgeslagen, dus er is geen migratie en geen herstart, en er zijn dertien types waaronder object- en multi-objectverwijzingen naar andere NetBox-records.

Twee velden daar, gegroepeerd onder een kopje “Asset” dat ik heb gekozen. Let op het paneel Dimensions rechts ervan: 9,5 kg, die niemand op dit apparaat heeft getypt. Ze kwamen van het apparaattype.
Custom fields worden gevalideerd, en het is de moeite te weten dat ze dat worden, want het is een echt verschil met de route hieronder. Ik heb het contractveld een reguliere expressie gegeven en de API dwong die af:
PATCH {"custom_fields": {"support_contract": "nonsense"}}
{"__all__": ["Invalid value for custom field 'support_contract':
Value must match regex '^[A-Z]{2,4}-[0-9]{6}$'"]}
Twee dingen zijn in 4.7 veranderd die op schaal uitmaken. Een veld aanmaken met een standaardwaarde, of een veld verwijderen, moet de opgeslagen data van elk object waarvoor het geldt herschrijven, dus op een grote tabel wordt dat werk aan een achtergrondtaak gegeven en meldt het veld “provisioning” of “deleting” terwijl die loopt. Een veld is alleen live zolang het actief is: tijdens een van die twee operaties verschijnt het niet op objecten, niet in formulieren, niet in filters en in geen van beide API’s. Daar moet een worker voor draaien, anders blijft het onbeperkt in die toestand.
Gebruik een custom field als je een feit over het ding zelf toevoegt. Gebruik een config context als de waarde moet doorsijpelen. Gebruik de volgende sectie als het ding dat je nodig hebt niet bestaat.
Eigen keuzemenu’s, en overschrijven wat wordt meegeleverd
Twee verschillende mechanismen wonen onder dit kopje en ze lossen verschillende problemen op. Het ene is voor je eigen velden. Het andere herschrijft die van NetBox.
Een choice set, voor je eigen keuzeveld
Een custom field van het type “selection” haalt zijn opties uit een choice set, een object dat je in de UI beheert zoals al het andere. Zo wordt “support tier” een echte dropdown in plaats van vrije tekst die iemand op drie manieren spelt.

Het wordt afgedwongen, ook via de API:
PATCH {"custom_fields": {"support_tier": "platinum"}}
{"__all__": ["Invalid value for custom field 'support_tier':
Invalid choice (platinum) for choice set Support tier."]}
Choice sets worden gedeeld, dus één set kan hetzelfde veld op meerdere modellen voeden, en de lijst op één plek wijzigen wijzigt hem overal.
FIELD_CHOICES, voor NetBox’ eigen velden
Dit is degene waarvan mensen niet weten dat hij bestaat. Meerdere ingebouwde keuzevelden van NetBox kunnen vanuit configuration.py worden uitgebreid of vervangen, waaronder apparaatstatus, sitestatus, rackstatus, circuitstatus en nog veel meer.12
Zet een plus erachter om toe te voegen aan wat wordt meegeleverd. Laat hem weg om de lijst helemaal te vervangen.
FIELD_CHOICES = {
# add to what NetBox ships with
'dcim.Device.status+': (
('burn-in', 'Burn-in', 'cyan'),
{'value': 'awaiting-rma', 'label': 'Awaiting RMA', 'color': 'orange',
'description': 'Faulty, with the vendor'},
),
# replace the stock list outright
'dcim.Site.status': (
('surveyed', 'Surveyed', 'purple'),
('building', 'Building out', 'orange'),
('active', 'Active', 'green'),
('closing', 'Closing', 'red'),
),
}
Precies dat heb ik op deze instantie gezet. De apparaatstatus kwam terug met de zeven standaardwaarden én mijn twee:
offline, active, planned, staged, failed, inventory, decommissioning,
burn-in, awaiting-rma
en de sitestatus kwam terug met alleen de mijne, de standaardlijst verdwenen:
surveyed, building, active, closing
Een keuze kan een eenvoudige tuple van waarde, label en kleur zijn, of een dictionary die ook een beschrijving neemt die als ondertitel in het formulier wordt getoond. En ze gedragen zich overal als native waarden, want voor de rest van NetBox zijn ze native waarden:

Die badge is mijn status, in mijn kleur, die sorteert en filtert als elke andere.
Drie dingen om te weten voordat je het gebruikt.
Vervangen verwijdert de standaardwaarden uit het menu, niet uit de database. Elk object dat er al een van heeft houdt hem, maar de waarde wordt niet meer aangeboden en is geen geldige keuze meer als iemand dat object de volgende keer bewerkt. Vervang je een lijst, controleer dan dat er niets op een waarde zit die je net hebt weggehaald.
Het staat in het configuratiebestand, dus het vraagt een herstart en het is niets wat een UI-gebruiker kan wijzigen. Voor een provider is dat de juiste kant op: welke statussen jouw bedrijf erkent is een governancebeslissing, geen beslissing op een dinsdagmiddag.
Breid uit voordat je vervangt. De standaardwaarden zijn wat elke plugin, elk script en elke integratie verwacht te zien. Toevoegen kost niets. Vervangen is een beslissing die je voor altijd bezit.
Custom objects
Hier is een echt gat. NetBox modelleert clusters, virtuele machines en virtuele disks. Het modelleert niet de datastore waar die disks echt op staan, en voor iedereen die Proxmox of VMware draait is dat het object dat de opslag die je hebt gekocht verbindt met de last die hem gebruikt.
De Custom Objects-plugin laat je een nieuw objecttype vanuit de UI of de API definiëren, zonder code te schrijven. Dus:

Zeven velden, waarvan twee het hele punt zijn. cluster is een enkelvoudige objectverwijzing naar een echt NetBox-cluster, op protect gezet zodat niemand een cluster onder zijn opslag weg kan verwijderen. provisioned_by is een multi-objectverwijzing naar de apparaten die hem werkelijk leveren.
Dat geeft je een eersteklas object met zijn eigen navigatie-item, lijstweergave, filters, import en export:

En omdat de verwijzingen echt zijn, laat de relatie zich ook van de andere kant zien. Open het cluster en de datastores staan ertegen vermeld.

Een custom object type erft het meeste van wat een NetBox-object een NetBox-object maakt: lijst- en detailweergaven, een navigatie-item, REST-endpoints, full-text zoeken, change logging, journaling, tags, bookmarks, import en export, event rules en meldingen. Niets daarvan hoefde geschreven te worden.
Het is ook geen JSON-blob die zich voordoet als tabel. De plugin zet echte DDL af, en wat in PostgreSQL verschijnt is een echte tabel met echte constraints:
Table "public.custom_objects_2"
Column | Type | Nullable | Default
-----------------+----------+----------+------------------
id | bigint | not null | identity
name | varchar | |
cluster_id | bigint | |
backing | varchar | |
capacity_gb | bigint | |
thin_provisioned| boolean | |
Indexes:
"custom_objects_2_name_key" UNIQUE CONSTRAINT, btree (name)
Foreign-key constraints:
... FOREIGN KEY (cluster_id) REFERENCES virtualization_cluster(id) ON DELETE RESTRICT
De protect waar ik om vroeg werd ON DELETE RESTRICT, en het werkt: dat cluster verwijderen komt terug als 409 met de naam van wat ervan afhangt.
Twee dingen om te weten voordat je erop vertrouwt
De validatie zit op het formulier, niet op de API. Dit is degene die een automatisering zou pakken. Ik heb required, een reguliere expressie en numerieke grenzen op de velden van een custom object type gedeclareerd, en er daarna via de REST-API naartoe geschreven:
| Wat ik declareerde | Wat ik stuurde | Resultaat |
|---|---|---|
validation_regex | een waarde die op niets past | 201 Created |
required: true | het veld helemaal weggelaten | 201 Created |
validation_minimum: 1 | een negatief getal | 201 Created |
unique: true | een duplicaat | 400, geweigerd |
on_delete_behavior: protect | het verwezen object verwijderen | 409, geweigerd |
De twee die hielden zijn de twee die databaseconstraints werden. De rest bestaat alleen op het webformulier, dus een mens die typt wordt beperkt en een nachtelijke sync niet. Vergelijk dat met het core custom field hierboven, waar dezelfde reguliere expressie via de API werd afgedwongen. Tot dat verandert: zet alles waar een factuur van afhangt achter een echt constraint.
Een type verwijderen laat een tabel vallen. Een veld verwijderen laat een kolom vallen. Dat is DDL vanuit een webformulier, uitgevoerd door wie de permissie ook heeft, en de documentatie zegt precies dat.13 Beperk wie deze mag verwijderen.
Wanneer je in plaats daarvan een echte plugin schrijft
Als het object belangrijk is, als automatisering erop schrijft, en als je het erg zou vinden er rommel in te vinden, schrijf het model dan zelf. Een minimale NetBox-plugin is een PluginConfig, een model, een serializer, een viewset, een tabel, een formulier, een paar views en een URL-map, en het komt op onder tweehonderd regels van overwegend declaraties. PrimaryModel subclassen geeft je gratis tags, custom fields, change logging, journaling, export-templates en eigenaarschap, en elk constraint dat je op het model zet wordt overal afgedwongen, want NetBox’ eigen serializer-machinerie voert het uit.
Er is een cookiecutter-template en een volledige plugin-tutorial, door de community onderhouden. Begin daarmee in plaats van met een lege map.
En het is de licentie die jij kiest, wat niemand later kan veranderen.
Custom validation en validatieklassen
Alles hierboven gaat over vastleggen wat er is. Dit gaat over weigeren vast te leggen wat er niet zou moeten zijn.
NetBox valideert elk object voordat het het schrijft, en je kunt er je eigen regels bovenop leggen. Er zijn drie mechanismen, ze wonen allemaal in configuration.py, en samen dekken ze bijna alles wat een huisstandaard nodig heeft.
Eén: eenvoudige regels, geen code
Een validator kan een eenvoudige toewijzing van veldnamen aan voorwaarden zijn. Geen Python, en het is overdraagbaar tussen installaties omdat het alleen data is.
CUSTOM_VALIDATORS = {
'dcim.site': (
{'description': {'required': True}},
),
'dcim.device': (
{'name': {'regex': r'^[a-z0-9]+-[a-z]+-([0-9]{2}|[a-z])$'}},
),
}
De beschikbare voorwaarden zijn min, max, min_length, max_length, regex, required, prohibited, eq en neq. Je kunt met een puntpad in een gerelateerd object reiken, dus region.name op een site mag, en je kunt op request.user.username matchen, al zegt de documentatie je volkomen terecht daarvoor liever permissions te gebruiken.
Beide regels vuren direct:
POST a site with no description
{"__all__": ["Custom validation failed for description:
['This field must not be empty.']"]}
POST a device named "Server1"
{"__all__": ["Custom validation failed for name: ['Enter a valid value.']"]}
Die tweede is een naamgevingsstandaard, afgedwongen. Niet op een wikipagina geschreven, niet in iemands hoofd, geen ding waarover de nieuwe drie weken later in een review wordt aangesproken. De database neemt het niet aan.
Twee: een validatorklasse, als de regel een zin is
Eenvoudige regels toetsen één veld tegen een constante. Echte huisregels zijn meestal voorwaardelijk: dit doet alleen mee als dat. Daarvoor subclass je CustomValidator, overschrijf je validate() en roep je fail() aan.
Ik heb er drie in een module op /opt/netbox/netbox/house_rules.py gezet:
from extras.validators import CustomValidator
class BillableKitNamesItsCustomer(CustomValidator):
"""Anything in a role we sell has to say whose it is."""
BILLABLE_ROLES = {'hypervisor'}
def validate(self, instance, request):
role = getattr(instance, 'role', None)
if role and role.slug in self.BILLABLE_ROLES and not instance.tenant:
self.fail(
f"A {role} is billable kit, so it must name the customer it belongs to.",
field='tenant',
)
class RackedDeviceNeedsAPosition(CustomValidator):
"""A device in a rack with no rack unit is a device nobody can find."""
def validate(self, instance, request):
if instance.rack and instance.position is None:
if instance.device_type and instance.device_type.u_height:
self.fail(
"A device in a rack needs a rack unit. Somebody has to find it.",
field='position',
)
en ze via een puntpad aangesloten:
CUSTOM_VALIDATORS = {
'dcim.device': (
{'name': {'regex': r'^[a-z0-9]+-[a-z]+-([0-9]{2}|[a-z])$'}},
'house_rules.BillableKitNamesItsCustomer',
'house_rules.RackedDeviceNeedsAPosition',
),
'ipam.prefix': (
'house_rules.CustomerPrefixNeedsATenant',
),
}
Merk op dat een model een tuple van validators neemt, eenvoudige regels en klassen vrij gemengd, en dat ze allemaal lopen. Zelfs een enkele validator moet als iterable worden doorgegeven, en dat zijn makkelijk vijf verloren minuten.
Dan doen de regels wat ze zeggen:
POST a hypervisor with no tenant
{"tenant": ["A Hypervisor is billable kit, so it must name
the customer it belongs to."]}
POST a device into a rack with no position
{"position": ["A device in a rack needs a rack unit.
Somebody has to find it."]}
POST a prefix with role "customer" and no tenant
{"tenant": ["A customer prefix must be assigned to a tenant."]}
POST a leaf switch with no tenant
accepted, because a leaf switch is not in BILLABLE_ROLES
Omdat fail() een field neemt, landt de melding op het juiste vakje in het formulier in plaats van bovenaan de pagina, en dat is het verschil tussen een regel die mensen leren en een regel die mensen kwalijk nemen.
Dat is ook het antwoord op het gat in de sectie over custom objects. De validatie daar bestond alleen op het webformulier. Een CustomValidator loopt in de modellaag, dus hij geldt voor de UI, de REST-API, GraphQL-mutaties, bulk-imports en alles wat een script doet. Er is één plek waar de regel wordt geschreven en geen manier eromheen.
Drie: protection rules, voor verwijderen
CUSTOM_VALIDATORS waakt over schrijfacties. PROTECTION_RULES waakt over verwijderacties, en neemt precies dezelfde twee vormen.
PROTECTION_RULES = {
'dcim.device': (
{'status': {'eq': 'offline'}},
),
}
Dat zegt dat een apparaat alleen verwijderd kan worden als het offline is. Wat dit oplevert:
DELETE an active device
{"detail": "Deletion is prevented by a protection rule:
[\"Custom validation failed for status:
['Ensure this value is equal to offline.']\"]"}
set it to offline, then DELETE
204 No Content
Twee toetsaanslagen wrijving tussen iemand en een live apparaat, en het is de goedkoopste verzekering in de hele applicatie. Maak het uitfaseringsproces tot het ding dat de verwijderknop ontgrendelt.
Degene die je gaat pakken
Bestaande data wordt niet gecontroleerd tot je hem de volgende keer aanraakt. Een regel toevoegen gaat niet terug om te valideren wat er al staat. Hij ligt stil tot iemand een object opslaat dat hem breekt, en dan krijgt die persoon een fout over een beslissing waar hij niets mee te maken had.
Ik heb het zien gebeuren. mcr1-hv-06 is aangemaakt voordat ik hier iets van had geschreven, als hypervisor zonder tenant. Hij stond daar volkomen tevreden. Toen bewerkte ik zijn beschrijving:
PATCH {"description": "touching it to trigger revalidation"}
{"tenant": ["A Hypervisor is billable kit, so it must name
the customer it belongs to."]}
De bewerking had niets met de tenant te maken. De regel vuurde toch, want de validatie loopt over het hele object.
Dat is correct gedrag en het is ook hoe een nieuwe regel in een supportticket verandert. Voordat je er een aanzet, vraag de objecten op die eraan zouden falen en repareer die eerst. De API maakt dat makkelijk: de regel is een filter, dus vraag de apparaten op met die rol en zonder tenant, en je hebt je lijst.
Zet de regel daarna aan. Dan pakt hij alleen nog nieuwe fouten, en daar is hij voor.
Het besturen vanuit Ansible
Een source of truth die niemand leest verrot. De collection netbox.netbox is hoe de meeste mensen dat voorkomen, en hij werkt in beide richtingen: NetBox vertelt Ansible wat er bestaat, en Ansible vertelt NetBox wat het heeft gebouwd.
Hij staat op versie 3.23.0, is gelicentieerd onder GPL-3.0, en is meer dan 13,4 miljoen keer van Galaxy gehaald. Hij draagt 91 modules en één inventory-plugin.14 Alles hieronder is uitgevoerd tegen diezelfde instantie, uit een container met niets anders erin dan ansible-core 2.21.4, pynetbox 7.8.0 en de collection.
De inventaris is een query, geen bestand
Dit is de helft die zich op de eerste middag terugbetaalt. nb_inventory bouwt je Ansible-inventaris rechtstreeks uit NetBox:
plugin: netbox.netbox.nb_inventory
api_endpoint: http://netbox:8080
token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
config_context: false
group_by:
- sites
- device_roles
- tenants
- racks
device_query_filters:
- has_primary_ip: true
Dat levert dit op, zonder dat iemand een hostlijst onderhoudt:
@sites_manchester-dc1: @device_roles_hypervisor:
|--mcr1-core-01 |--mcr1-hv-01
|--mcr1-core-02 |--mcr1-hv-02
|--mcr1-hv-01 |--mcr1-hv-03
|--mcr1-hv-02 |--mcr1-hv-04
... |--mcr1-hv-05
@racks_MCR1-A07: @tenants_ravenscroft-legal:
|--mcr1-core-01 |--mcr1-hv-01
|--mcr1-core-02 |--mcr1-hv-02
... @tenants_padgate-foods:
@device_roles_core-router: |--mcr1-hv-03
|--mcr1-core-01 |--mcr1-hv-04
|--mcr1-core-02 @tenants_hartley-components:
|--mcr1-hv-05
Kijk naar de rechterkolom. Omdat tenancy op de apparaten is gezet, krijg je gratis een groep per klant, dus --limit tenants_ravenscroft-legal voert een play uit tegen precies de apparatuur van één klant. Voeg een apparaat toe in NetBox en het zit bij de volgende run in de groep. Faseer er een uit en het is weg. Niemand bewerkt iets.
Elke host komt aan met wat NetBox over hem weet:
ansible_host "2001:db8:20:20::11"
primary_ip4 "10.20.20.11"
primary_ip6 "2001:db8:20:20::11"
device_roles ["hypervisor"]
sites ["manchester-dc1"]
racks ["MCR1-A07"]
tenants ["ravenscroft-legal"]
device_types ["sys-1029u-tn10rt"]
manufacturers ["supermicro"]
status {"label": "Active", "value": "active"}
Tweeëntwintig sleutels in totaal, en één ervan is het waard op te merken voordat hij je verrast. ansible_host is het IPv6-adres, omdat dat apparaat een primair v6 heeft staan. Ik heb het nagekeken tegen twee andere die alleen v4 hebben en die kwamen terug met v4, dus de regel is dat de plugin v6 verkiest waar een primair v6 bestaat. Wat correct is, en ook het soort ding dat je liever nu ontdekt dan terwijl je je afvraagt waarom een play verbindt over een pad waar je niet aan had gedacht.
Terugschrijven
De modules zijn de andere richting, en degene die je wil hebben is IP-uitgifte, want NetBox weet wat vrij is en jouw playbook niet.
- name: Rack the device
netbox.netbox.netbox_device:
netbox_url: "{{ nb_url }}"
netbox_token: "{{ nb_token }}"
data:
name: mcr1-hv-07
device_type: SYS-1029U-TN10RT
device_role: Hypervisor
site: Manchester DC1
rack: MCR1-A07
position: 14
face: Front
tenant: Hartley Components
state: present
- name: Let NetBox pick the next free address out of the customer prefix
netbox.netbox.netbox_ip_address:
netbox_url: "{{ nb_url }}"
netbox_token: "{{ nb_token }}"
data:
prefix: 10.20.22.0/24
tenant: Hartley Components
assigned_object:
device: mcr1-hv-07
name: eno1
state: new
Let op wat er niet in staat. Geen IP-adres. Je noemt het prefix en NetBox geeft het volgende vrije terug:
TASK [Let NetBox pick the next free address out of the customer prefix] ****
changed: [localhost]
"device : mcr1-hv-07 (created)"
"interface: eno1 (created)"
"address : 10.20.22.1/24 (allocated)"
En een seconde later staat het in de UI, nog aan niets gekabeld maar ingeracked, met tenant en geadresseerd:

De valkuil in dat playbook
Laat het een tweede keer lopen zonder één regel te wijzigen en dit gebeurt:
"device : mcr1-hv-07 (already correct)"
"interface: eno1 (already correct)"
"address : 10.20.22.2/24 (allocated)"
Het apparaat en de interface zijn idempotent. Het adres niet, en dat is geen bug. state: present betekent “maak dat het er zo uitziet”. state: new betekent “geef me een nieuwe”, elke keer weer, en dat is precies wat het deed: twee adressen op één interface na twee runs.
Dat is prima als je echt iets nieuws uitrolt, en het eet stilletjes een prefix op als je het in een taak zet die elke nacht loopt. Geef één keer uit en leg het resultaat vast, of gebruik state: present met het adres dat je al hebt. De module doet wat je vroeg. De vraag is of je vroeg wat je bedoelde.
Het is niet alleen Ansible
De collection krijgt de aandacht omdat Ansible is waar de meeste netwerkteams al zitten, maar de API is het product en genoeg dingen spreken hem.
| Ding | Wat het is | Licentie |
|---|---|---|
pynetbox | De Python-client die de collection zelf gebruikt | Apache 2.0 |
terraform-provider-netbox | NetBox-objecten als Terraform-resources beheren, actief onderhouden door e-breuninger | MPL 2.0 |
nornir_netbox | NetBox als Nornir-inventaris, voor wie Python doet in plaats van YAML | Apache 2.0 |
| Prometheus SD | Levert Prometheus zijn scrape-targets uit NetBox | MIT |
go-netbox | Een Go-client, al is er sinds mei 2025 niet aan geraakt | zie repository |
| Diode | NetBox Labs’ eigen ingestion-pijplijn om ontdekte data naar binnen te duwen | NetBox Limited Use |
Die Terraform-regel is de interessante voor wie zijn infrastructuur al zo beheert, want hij laat één plan de cloudresource en het NetBox-record dat hem documenteert aanmaken, in plaats van de tweede helft aan iemands geheugen te laten.15
Diode draagt dezelfde licentie als Branching en Custom Objects, dus dezelfde lezing geldt voordat je erop bouwt.
Wat die middag echt koopt
Alles hierboven kostte me een dag op één machine, en het grootste deel van die dag ging op aan data zaaien zodat de schermen iets bevatten. De installatie is twintig minuten, welke route je ook neemt. De beslissingen zijn het deel dat uitmaakt en ze worden allemaal in het eerste uur genomen: tenants voor alles, apparaattypes voor apparaten, de cijfers op de types in plaats van op de apparatuur.
Wat je daarvoor krijgt is geen documentatie. Dat onderscheid maakt niemand voordat hij beide heeft gehad: documentatie is iets wat je schrijft en daarna niet meer onderhoudt. Wat je krijgt is een database die weigert een tegenspraak te bevatten: hij laat je geen twee dingen in één rack-unit zetten, of een rack in een locatie die bij een andere site hoort, of een apparaat op een type dat niet bestaat. Elk van die weigeringen is een discussie die je over zes maanden niet hebt.
En het vertelt je dingen die niemand heeft gevraagd. Die kast zit 28,6% vol met apparatuur en 90,7% vol met stroom. Niemand was dat gaan zoeken. Het viel eruit omdat er één keer een wattage op een apparaattype is gezet, en het is het verschil tussen de volgende order daar inracken en het op de harde manier ontdekken.
De eerlijke kanttekening is dezelfde die elke source of truth heeft. Hij is alleen waard wat het je kost om hem juist te houden, en de enige versie hiervan die een druk kwartaal overleeft is die waarin er zichtbaar iets stukgaat als de data verkeerd is. Knoop je monitoring, je provisioning of je firewallregels eraan vast zodat ze eruit lezen, en een verkeerde invoer is geen documentatieprobleem meer waar iemand ooit aan toekomt. Het wordt een storing om half tien op een dinsdag, met een naam eraan. Dat klinkt als een kostenpost. Het is het hele mechanisme.
Niemand gaat je er ook voor bedanken. Een juist record laat zich zien als de migratie die veertien dagen duurde in plaats van een kwartaal, de audit die een middag duurde, de klant die aan de telefoon een recht antwoord kreeg. Niets daarvan verschijnt in een rapport naast je naam.
Doe het toch. Het alternatief is een bedrijf dat zichzelf niet kan beschrijven, en een bedrijf dat zichzelf niet kan beschrijven wordt niet geleid. Het wordt herinnerd, door elk jaar minder mensen.
NetBox, Introduction — “Today, the open source project is stewarded by NetBox Labs and a team of volunteer maintainers”, en de herkomst bij DigitalOcean in 2015, open source sinds juni 2016. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
NetBox, LICENSE.txt — Apache License 2.0, copyright DigitalOcean, LLC. Herkomst en beheer uit de inleiding. ↩︎ ↩︎
Releasenotes van Nautobot v1.0.0 — “a divergent fork of NetBox 2.10”, gepubliceerd op 26 april 2021, en de lijst van wat het toevoegde ten opzichte van NetBox 2.10. ↩︎ ↩︎
Network to Code, “Why Did Network to Code Fork NetBox?”, 25 februari 2021 — de redenering over SLA’s en langetermijnondersteuning, de uiteenlopende visie, en “the NetBox project team suggested that we should consider forking”. ↩︎
NetBox Labs is in 2023 opgericht als spin-out uit NS1 na de overname door IBM, mede-opgericht door NetBox’ lead maintainer Jeremy Stretch, en kondigde in april 2023 een Series A van 20 miljoen dollar aan: NetBox Labs, “Let’s Go: Announcing NetBox Labs” en de aankondiging van de Series A. ↩︎
NetBox Labs, NetBox Enterprise — de zelfbeheerde commerciële editie, met “24/7 expert assistance from the NetBox Labs team”; NetBox Cloud is het gehoste aanbod. Concrete SLA-voorwaarden staan niet op die pagina. ↩︎
Aantallen sterren en forks, releasedata en licenties van beide projecten op 30 september 2026 via de GitHub-API afgelezen: netbox-community/netbox en nautobot/nautobot. Nautobots parallelle releases 3.2.x en 2.4.x zijn beide gedateerd 28 september 2026. ↩︎
netbox-community/netbox-docker — de container-stack van de community, Apache 2.0. ↩︎
NetBox, Installation — de tabel met ondersteunde versies, en de releasenotes van v4.7 voor de verhoogde minima van PostgreSQL en Redis. Versie en editie afgelezen uit
netbox/release.yaml. ↩︎NetBox Limited Use License 1.0 — gedragen door netbox-custom-objects en, identiek, door netbox-branching. Beide geciteerde clausules zijn letterlijk. ↩︎
NetBox, HTTP Server installation, “What’s Next?” — “Some of the most popular plugins include” NetBox Branching, NetBox Custom Objects, NetBox DNS en NetBox BGP, zonder enige vermelding van licentievoorwaarden. ↩︎
NetBox, Data Validation configuration —
FIELD_CHOICES, en het plus-achtervoegsel dat uitbreidt in plaats van vervangt: “To replace the available choices, specify the app, model, and field name separated by dots … To extend the available choices, append a plus sign”. ↩︎Documentatie van netboxlabs/netbox-custom-objects — “Deleting a Custom Object Type drops an entire database table and should be done with caution.” ↩︎
netbox.netbox op Ansible Galaxy en netbox-community/ansible_modules — versie 3.23.0, GPL-3.0, 91 modules plus de inventory-plugin
nb_inventory. Downloadaantal op 30 september 2026 op Galaxy afgelezen. Alles wat is getoond liep met ansible-core 2.21.4 en pynetbox 7.8.0. ↩︎Licenties, activiteit en aantallen sterren op 30 september 2026 bij elk project afgelezen: pynetbox (Apache 2.0), terraform-provider-netbox (MPL 2.0, v6.0.0-rc.1 in het Terraform-register), nornir_netbox (Apache 2.0), netbox-plugin-prometheus-sd (MIT), go-netbox (laatste push 9 mei 2025) en Diode, dat dezelfde NetBox Limited Use License 1.0 draagt als Branching en Custom Objects. ↩︎