How bootstrap/bootstrap.sh populates Nautobot with the reference data
MNM needs, and how to extend it when a new vendor / model surfaces.
- Operators: how to re-run bootstrap, how to fix
MissingReferenceErrorfrom onboarding, how to add a new vendor. - Engineers: how the script is structured, where to add entries.
bootstrap/bootstrap.sh runs once after docker compose up -d brings
Nautobot up. It is idempotent — every step checks if the target
already exists and skips it cleanly. Safe to re-run.
The script creates, in order:
mnm_controllerPostgreSQL database on the existingmnm-postgresinstance (controller's persistent storage).- Nautobot superuser from
MNM_ADMIN_*env vars. - API token for that superuser.
- Location Types (Region → Site) and default Region + Site.
- Device Roles: Router, Switch, Firewall, Access Point, Endpoint, Unknown.
- Manufacturers: Juniper, Cisco, Cisco Meraki, Fortinet, Arista, Palo Alto Networks, Aruba, Extreme Networks, MikroTik, Ubiquiti, Huawei.
- Platforms — one per
(network_driver, napalm_driver)combo (see Adding a Platform below). - Devicetype Library Git repo registration for the welcome-wizard plugin, plus a sync trigger.
- Bulk DeviceType import from the synced devicetype-library (~5,200 device types from the netbox-community library).
- Lab-only virtual DeviceTypes that the community library does not
ship — currently
vEOS-lab(Arista) andC8000V(Cisco). See Adding a DeviceType below. - NAPALM credential Secrets + Secrets Group (only if
NAUTOBOT_NAPALM_USERNAME/_PASSWORDare set). - Discovery custom fields on the IP Address model (sweep + endpoint enrichment).
bash bootstrap/bootstrap.shThe script fast-skips if Nautobot already has > 5,000 device types
and the endpoint_data_source custom field exists. Force a full
re-run with:
MNM_BOOTSTRAP_SKIP_CHECK=1 bash bootstrap/bootstrap.shUse the force flag after extending the script (e.g. adding a Platform or DeviceType row) so the new entries land on an already-bootstrapped install.
Direct-REST onboarding (see docs/ONBOARDING.md) validates every Nautobot reference (Role, DeviceType, Platform, Active Status) before Step A. If any are missing, the orchestrator returns a structured error like:
MissingReferenceError: Cannot onboard: required Nautobot reference
data missing.
Vendor: cisco; Chassis model: C8000V
Missing entries:
- DeviceType: C8000V
Fix: POST /api/dcim/device-types/ {"model": "C8000V",
"manufacturer": "<Cisco UUID>", "u_height": 0}
(or add to LAB_DEVICETYPES in bootstrap/bootstrap.sh)
- Platform: cisco_iosxe
Fix: POST /api/dcim/platforms/ {"name": "cisco_iosxe",
"network_driver": "cisco_iosxe", "napalm_driver": "<ios|junos|...>"}
(or add to PLATFORMS in bootstrap/bootstrap.sh)
After adding the missing entries, retry onboarding. To make the fix
permanent, add the entry to bootstrap/bootstrap.sh (see
docs/BOOTSTRAP.md).
Each missing entry includes a one-line REST command you can paste into
curl (or the Nautobot API browser) to fix immediately, plus the
bootstrap section to extend for a permanent fix.
Why no auto-create? MNM Rule 5 (Pre-load all reference data). Auto- creating missing references on-the-fly would hide real data-quality issues — you'd never know the bootstrap was incomplete, and every fresh install would silently fix itself differently. Explicit failure
- one-line fix is better than implicit silent recovery.
Every Platform pre-loaded by bootstrap appears in section 4b of
bootstrap/bootstrap.sh as a network_driver|napalm_driver|display| manufacturer row in the PLATFORMS array.
To add a new one:
-
Confirm the NAPALM driver is installed in
nautobot/Dockerfile(every PLATFORMS row'snapalm_drivermust resolve to a driver class that loads, otherwise Polling raises at first use). -
Append a row to the
PLATFORMSarray. Format:"<network_driver>|<napalm_driver>|<display name>|<manufacturer>" -
The
network_drivervalue must match whatcontroller/app/onboarding/classifier.pyreturns asClassifierResult.platform(e.g.cisco_iosxe,juniper_junos). -
The
manufacturermust already exist in theMFG_NAMESarray earlier in the script. -
Re-run with
MNM_BOOTSTRAP_SKIP_CHECK=1.
Example: adding Aruba AOS-CX (already in the array as a reference):
"aruba_aoscx|ios|Aruba AOS-CX|Aruba"
There are two paths depending on whether the model is in the netbox-community/devicetype-library:
The bulk import (bootstrap/import_devicetypes.py) picks it up
automatically on the next bootstrap run. If your model isn't appearing,
re-trigger the Git repo sync:
# Inside Nautobot UI: Extensibility > Git Repositories >
# "Devicetype Library" > Syncthen re-run bootstrap with MNM_BOOTSTRAP_SKIP_CHECK=1.
Append to the LAB_DEVICETYPES array in section 6b of
bootstrap/bootstrap.sh. Format:
"<model>|<manufacturer>"
The script creates the DeviceType with u_height=0 (treats it as
virtual / nominal-spec). Phase 2 of onboarding walks ifTable via
SNMP to populate the actual interface set, so the DeviceType record
itself only needs to satisfy the foreign-key constraint on Device
creation.
Example (already shipped in v1.0):
"vEOS-lab|Arista"
"C8000V|Cisco"
Re-run with MNM_BOOTSTRAP_SKIP_CHECK=1.
If you need richer DeviceType records (specific port templates, power draw, console ports), use the Nautobot UI to create them and let the operator workflow document the addition manually. The bootstrap script focuses on the minimum viable record so onboarding doesn't 400.
The netbox-community DeviceType library occasionally uses slightly different manufacturer name spellings than the canonical entries bootstrap section 4 creates. The known case:
| Library imports | Bootstrap section 4 creates |
|---|---|
Palo Alto |
Palo Alto Networks |
When both records coexist, Nautobot's platform–manufacturer validation rejects device creation at Step A:
"platform is limited to Palo Alto Networks device types,
but this device's type belongs to Palo Alto"
Bootstrap section 6a closes this gap with a reassign-then-delete
pass after the bulk import. For each entry in the
MANUFACTURER_DEDUP_PAIRS array it:
- Looks up both manufacturer IDs.
- Pages through every DeviceType associated with the duplicate
and
PATCHes each one to point at the canonical manufacturer. - Verifies zero DeviceTypes remain under the duplicate before deleting — if any remain (e.g. a PATCH failed mid-batch), the delete is skipped and a warning is logged.
- Deletes the now-orphaned duplicate manufacturer.
The pass is idempotent: when the duplicate is already absent (e.g. on a re-run) it logs a skip and returns cleanly.
If a future library update introduces another manufacturer-name
inconsistency, add one entry to the MANUFACTURER_DEDUP_PAIRS
array in bootstrap/bootstrap.sh section 6a:
MANUFACTURER_DEDUP_PAIRS=(
"Palo Alto|Palo Alto Networks"
"Duplicate Name|Canonical Name" # ← add here
)Then re-run bootstrap with MNM_BOOTSTRAP_SKIP_CHECK=1 to apply
the new entry on an already-bootstrapped install.
bootstrap/tests/test_bootstrap_dedup.sh is a live integration
test that requires a running Nautobot container. It creates
synthetic manufacturer records and a DeviceType, exercises the same
dedup logic, asserts the expected outcomes (reassign + delete,
idempotency, canonical-missing skip), and cleans up. Run from the
project root:
bash bootstrap/tests/test_bootstrap_dedup.shBoth are simple for ... do api_create ... loops earlier in the
script. Add to the relevant array, re-run.
Bootstrap runs once on first install, then occasionally after schema extensions. It needs to:
- Wait for Nautobot's container to become healthy.
- Talk to Nautobot's API from outside the container (different network namespace from the controller).
- Be runnable on a fresh box that has Docker but not yet a Python virtualenv set up.
A shell script with docker exec ... curl ... calls fits these
constraints cleanly. The DeviceType bulk-import is the only Python
piece (bootstrap/import_devicetypes.py) and runs inside the Nautobot
container via nautobot-server nbshell to use the welcome-wizard
plugin's models directly.