Sync NetBox to Infrahub
This tutorial is for people getting started with Infrahub: it starts from a blank page, with no existing Infrahub instance or schema required. We will use Infrahub Sync to copy data from the public NetBox demo into Infrahub, installing and configuring everything we need along the way. This tutorial covers the fundamental work needed to get started; more advanced topics will be covered in subsequent guides.
If you already have an Infrahub instance running, you can skip straight to the NetBox adapter documentation.
By the end of this tutorial, you will know how to:
- run Infrahub in a Docker container
- install Infrahub Sync
- load a schema into Infrahub
- create a NetBox → Infrahub sync project
- synchronize NetBox objects into Infrahub
- review the import in a branch and open a proposed change
Time: ~30 minutes
What you will build: A running Infrahub instance with a production-grade schema covering the same core domains as NetBox (locations, devices, interfaces, IPAM, and organizations), populated with data synchronized from the public NetBox demo into a branch, ready to review as a proposed change.
Prerequisites:
- Docker installed and running (Docker Desktop or OrbStack)
- uv (Python package manager)
- Python 3.11+
The NetBox demo instance is public and resets regularly. Object counts, names, and sample data may differ from the examples in this tutorial. Because anyone can edit it, it can also contain malformed or unexpected data that breaks the sync — see Troubleshooting if you run into errors.
Create a project
Copier is a project scaffolding tool. Use it to create a new Infrahub project from the official template, which includes the standard file structure, task definitions, and a schemas/ folder:
- Run the following command to create a new project directory:
uv tool run --from 'copier' copier copy https://github.com/opsmill/infrahub-template infrahub-automation
When prompted, enter a project name (for example, infrahub-automation), then press Enter to accept the default for every remaining prompt (they all default to No).
- Navigate to the project directory:
cd infrahub-automation
- Open the project in your IDE. If you have Visual Studio Code installed, you can run:
code .
Run ls in the project directory. You should see files including pyproject.toml, tasks.py, and a schemas/ folder.
Start Infrahub
The project template includes Invoke tasks that wrap Docker Compose commands.
- Start all Infrahub services with a single command:
uv run invoke start
The first run takes a few minutes while Docker downloads the container images.
-
Open your browser and go to http://localhost:8000.
-
Log in from the bottom-left corner using the default credentials:
- Username:
admin - Password:
infrahub
- Username:
You should see the Infrahub web interface with a navigation menu on the left side.
Install infrahub-sync
- Install the Python dependencies with uv. This tutorial documents behavior from the v3 development branch, which is not yet published to PyPI, so install directly from the repository:
uv add "infrahub-sync @ git+https://github.com/opsmill/infrahub-sync.git@feature/v3-develop" pynetbox
This installs the infrahub-sync command. pynetbox is required by the NetBox adapter.
- Verify infrahub-sync command is available:
uv run infrahub-sync --help
For more about supported adapters and their Python requirements, see the NetBox adapter documentation.
Load a schema into Infrahub
Infrahub stores data according to its schema. Before we can import any data, we need Infrahub to know the kinds of objects that the sync will create.
We provide a production-grade Infrahub schema covering DCIM and IPAM features similar to NetBox's. It is not a one-to-one port of NetBox's own data model, and that's intentional.
This is a production-grade Infrahub schema, not a copy of NetBox's data model. Infrahub gives you a flexible graph model, so a real migration can preserve the parts of NetBox that matter to you while adapting the model to your own workflows.
This collection of schemas is maintained separately from Infrahub Sync. Download the exact source revision verified with this configuration so the destination kind names and relationships remain compatible:
SCHEMA_LIBRARY_REF="893be13f465158fdf49ce4133a501c8355d53a32"
DEST="schemas"
BASE_URL="https://raw.githubusercontent.com/opsmill/schema-library/${SCHEMA_LIBRARY_REF}"
FILES=(
"base/dcim.yml"
"base/location.yml"
"base/ipam.yml"
"base/organization.yml"
"extensions/aggregate/aggregate.yml"
"extensions/cable/cable.yml"
"extensions/circuit/circuit.yml"
"extensions/compute/compute.yml"
"extensions/cluster/cluster.yml"
"extensions/hosting_cluster/hosting_cluster.yml"
"extensions/lag/lag.yml"
"extensions/location_site/location_site.yml"
"extensions/vlan/vlan.yml"
"extensions/qinq/qinq.yml"
"extensions/rack/rack.yml"
"extensions/vrf/vrf.yml"
)
for f in "${FILES[@]}"; do
curl -sSL --create-dirs -o "${DEST}/${f}" "${BASE_URL}/${f}"
done
Do not substitute models/examples/netbox/netbox.yml from the Infrahub source
repository. Despite its similar name, it defines a different model whose kinds do not
match this sync configuration.
Export the local Infrahub address and API token. Infrahub Sync will need them later to authenticate against your instance:
export INFRAHUB_ADDRESS="http://localhost:8000"
export INFRAHUB_API_TOKEN="06438eb2-8019-4776-878c-0941b1f1d1ec"
This is the default admin token that the project's Docker Compose stack seeds automatically, and it matches the default already configured in infrahubctl.toml. Do not reuse it for an internet-facing or shared Infrahub instance.
Then load the schema into Infrahub using the project's load-schema task:
uv run invoke load-schema
Refresh the local Infrahub web interface. You should see additional schema objects in the left navigation. You can also open the schema view in the UI to explore the kinds and relationships that were loaded.
Create a NetBox API token
Create a token in the public NetBox demo instance so Infrahub Sync can read data from NetBox.
- Open the public NetBox demo.
- Log in with username
adminand passwordadmin. - Open your user profile.
- Create an API token.
- Copy the complete generated token value.
Export the token locally:
export NETBOX_URL="https://demo.netbox.dev"
export NETBOX_TOKEN="nbt_..."
Export the complete nbt_... token value. Do not include an authorization prefix such as Bearer, and do not copy only the short Key field.
Create the sync project
A sync project is a directory containing a config.yml file. The configuration names the source and destination adapters, maps source fields to destination fields, and includes references that let Infrahub Sync compute write order.
From your project directory (infrahub-automation), create a sync project directory and download the example NetBox to Infrahub configuration.
mkdir -p sync-projects/netbox-demo
curl -L \
https://raw.githubusercontent.com/opsmill/infrahub-sync/refs/heads/main/examples/netbox_to_infrahub/config.yml \
-o sync-projects/netbox-demo/config.yml
The downloaded configuration is named from-netbox. It maps selected NetBox objects onto the Infrahub schema.
Open sync-projects/netbox-demo/config.yml in an editor and scan the top-level keys:
nameidentifies the sync project.sourceconfigures the NetBox adapter.destinationconfigures the Infrahub adapter.schema_mappingdefines how NetBox API resources become Infrahub objects.
The configuration includes default endpoint values, but the environment variables exported above take precedence for tokens and URLs.
The example configuration comments also describe saved-plan review and apply. This
tutorial first uses diff to inspect the current systems, then uses sync --diff to load
them again, calculate a new plan, print it, and apply it. See
Run a sync when you need to apply the exact saved plan you
reviewed instead of recalculating it.
For a fuller explanation of this file, see Create a sync project and the schema mapping reference.
Generate the sync code
Generate the Python models and adapter code for this sync project.
uv run infrahub-sync generate --name from-netbox --directory sync-projects
The generate command reads the sync configuration and the destination schema, then writes the Python code used at runtime. Run generate again any time you edit config.yml.
For more about the command flow, see Run a sync.
Create a branch
Infrahub tracks changes through branches, so you can review a batch of imported data as a proposed change before it lands on main. Create a branch to hold this import:
uv run infrahubctl branch create netbox-import
The rest of this tutorial runs diff and sync against this branch with
--branch netbox-import, so you can inspect the destination changes before merging the
Infrahub branch. For more about branches and proposed changes, see
Infrahub version control.
Preview the changes
Run a dry-run diff against the netbox-import branch before writing any data to Infrahub.
uv run infrahub-sync diff --name from-netbox --directory sync-projects --branch netbox-import
The diff command loads data from NetBox and Infrahub, compares both sides, and prints the
planned changes. It also saves a plan artifact under
.infrahub-sync-cache/from-netbox/<run-id>/plan/, which you can review on its own with
infrahub-sync diff --name from-netbox --directory sync-projects --from-plan <run-id>.
The sync command in the next step creates and applies a new plan; it does not apply this
saved artifact.
Review the output before continuing. On a first run against an empty branch, most planned changes should be creates. Exact counts depend on the current public NetBox demo data.
Sync the data
After inspecting the diff, run the sync against the same branch. The command loads both systems again and prints the newly calculated plan before writing.
uv run infrahub-sync sync --name from-netbox --directory sync-projects --branch netbox-import --diff
The --diff option prints the diff before applying the changes. The first sync can take a few minutes because it writes the imported objects and relationships into the netbox-import branch — main is untouched until you merge the resulting proposed change. Because this configuration omits order:, Infrahub Sync derives the write order from the mapping references.
diff against this branch fails once interfaces existOnce the sync has written interfaces, running diff or sync against the same branch again fails while loading the destination. This is a known defect, and it is not specific to NetBox. See diff fails after a sync that wrote interfaces for the error and how to get moving again.
Verify the imported data
The imported data lives on the netbox-import branch, not on main. Open the local Infrahub web interface:
-
Use the branch selector in the top-left corner to switch from
maintonetbox-import. -
Browse the left navigation for imported objects from the NetBox demo. Depending on the current demo data and the example mapping, you may see objects such as:
- tags and organizations (manufacturers, providers, RIRs)
- sites and racks
- devices and interfaces
- VRFs, VLANs, and VLAN groups
- prefixes, IP addresses, and aggregates
- circuits
This is a minimal mappingSome records and relationships may be missing — that doesn't mean the sync failed.
-
Once you're happy with the data, open a proposed change from
netbox-importtowardmainso the import can be reviewed before it's merged.
If you switch back to main, none of this data is there yet — that's expected, since it's still isolated in the branch.
What happened
You used Infrahub Sync to move data from NetBox into Infrahub in a controlled sequence:
- Infrahub provided the destination graph and schema.
- NetBox provided the source data.
- A branch isolated the import from
mainso it could be reviewed first. config.ymldescribed the adapters, field mappings, references, filters, and Transformations.generateconverted the configuration into runnable sync code.diffcompared the source and destination without writing changes.syncapplied the reviewed changes to the branch.
The same pattern applies to larger migrations: start with a clear schema, map a small set of objects, condition source data where needed, review the diff inside a branch, and then synchronize before opening a proposed change toward main.
Stop the local Infrahub instance
When you are finished, stop the Docker Compose stack.
uv run invoke stop
Troubleshooting
infrahub-sync fails with Both url and token must be specified
ERROR | infrahub_sync.cli | Failed to initialize the Sync Instance: Error initializing InfrahubAdapter: Both url and token must be specified!
The NetBox and/or Infrahub adapter can't find credentials. generate, diff, and sync read the source and destination credentials from the environment, not from config.yml. apply reads only the destination credentials because it uses the saved plan rather than loading the source. Export the variables needed for the command you are running:
export NETBOX_URL="https://demo.netbox.dev"
export NETBOX_TOKEN="<your-netbox-token>"
export INFRAHUB_ADDRESS="http://localhost:8000"
export INFRAHUB_API_TOKEN="06438eb2-8019-4776-878c-0941b1f1d1ec"
This error names whichever adapter (NetBox or Infrahub) is missing its variables — the same message is raised for either one.
generate reports destination models missing from the schema
ERROR | One or more models are not present in the schema - ['LocationSite', ...]
The destination does not have the schema collection this configuration maps to. Download
and load every file listed in Load a schema into Infrahub,
then run generate again. Use a fresh Infrahub instance or branch if an existing
InfraDevice or another overlapping kind prevents that collection from loading.
The similarly named models/examples/netbox/netbox.yml file in the Infrahub source
repository is not a replacement: it uses a different set of destination kinds.
infrahub-sync fails with Object ... already present
ValueError: An error occurred while loading Netbox: ('Object 172.16.0.2/24__Alpha already present', IpamIPAddress "172.16.0.2/24__Alpha")
Two source objects map to the same identifier — here, two IP addresses sharing an address and VRF. Delete one of the duplicates in NetBox (IPAM > IP Addresses, search the address from the message), then re-run the command.
diff fails after a sync that wrote interfaces
ValueError: An error occurred while loading Infrahub: Cannot build unique_id for peer
InterfaceLag[18c6…] (relationship InterfacePhysical.bundle, parent id=18c6…): missing
identifier key(s) ['device']; required identifiers=['device', 'name'], present
keys=['local_id', 'name', 'description', ...]
A first diff succeeds. Once a sync or apply has written an InterfacePhysical whose bundle points at an InterfaceLag, every later diff against that branch fails — while loading the destination, before any plan is produced. The message names Infrahub, not Netbox: the source is not involved. Deleting the interface objects at the destination restores normal behavior, and the cycle repeats.
The cause is that the destination loader rebuilds each related peer's identifier from a cached copy of the peer node, and the copy it finds carries the peer's attributes but not its relationships. InterfaceLag is identified by device and name, so its identifier cannot be rebuilt and the load aborts.
diff has no --continue-on-error option — the flag exists only on sync, so the advice in the error message does not apply when the failure came from diff. There are two ways forward, depending on whether you need a plan you can review before it is applied.
To keep the plan, review, and apply loop: delete the interface objects at the destination, then re-run diff. Those objects are re-imported by the next sync.
If you do not need to review the plan first: run sync with the flag, which loads, plans, and applies in one step.
uv run infrahub-sync sync --name from-netbox --directory sync-projects --branch netbox-import --continue-on-error
The unresolvable peer link is logged and skipped rather than aborting the run. The cost is that the affected interface's bundle peer is then absent from the extracted destination state, so the run carries an update for that object every time, even when nothing changed upstream. Read the warnings before relying on the result.
Two things to be clear about:
- This is not a NetBox-specific defect, and it is not introduced by the saved-plan workflow. It affects any configuration in which a mapped kind references a peer kind whose
identifiersinclude a relationship — thenautobot,ipfabric,slurpit, andaciexample configurations all contain that shape. --continue-on-erroris asyncoption, and it only covers this loading failure. It does not make a run tolerant of everything: a source record that references a peer the source itself cannot resolve still fails the whole run when the plan is derived, by design. Use it for this destination-loading case, not as a general "keep going" switch.
Next steps
Now that you have completed a first sync, you have covered some of the basic objects in NetBox.
This is only the first step. You will likely want to bring in the parts that are unique to your own NetBox instance (for example, roles or custom fields).
Other guides will soon be available to cover:
- How to sync locations/regions
- How to deal with VLAN/Prefix/Device roles
- How to cover custom attributes / relationships
- Migrate configuration context