Skip to content

Migrating to Nautobot from NetBox

Review the Release Notes

Be sure to carefully review all release notes that have been published. In particular, the Nautobot 1.0 release notes include an overview of key changes between NetBox 2.10 and Nautobot 1.0, while later release notes highlight incremental changes between Nautobot versions.

Install Nautobot

Install Nautobot as described in the documentation.

Configure Nautobot

Although Nautobot will run perfectly well with a default configuration (such as generated by nautobot-server init, you may want to replicate aspects of your previous NetBox configuration to Nautobot. Refer to the configuration documentation for details on the available options.

Migrate Database Contents Using nautobot-netbox-importer

Due to a number of significant infrastructural changes between the applications, you cannot simply point Nautobot at your existing NetBox PostgreSQL database and have it automatically load your data. Fortunately, Network to Code (NTC) and collaborators have developed a Nautobot plugin, nautobot-netbox-importer, that can be used to import a NetBox database dump file into Nautobot. For full details, refer to the plugin's own documentation, but here is a brief overview:

  1. Export your NetBox database to a JSON file.
  2. Install the importer plugin.
  3. Enable the importer plugin.
  4. Run the plugin's import command to import the data.
  5. Connect to Nautobot and verify that your data has been successfully imported.

Migrate Files from NetBox to Nautobot

Uploaded media (device images, etc.) are stored on the filesystem rather than in the database and hence need to be migrated separately. The same is true for custom scripts and reports that you may wish to import.

Copy Uploaded Media

The exact command will depend on where your MEDIA_ROOT is configured in NetBox as well as where it's configured in Nautobot, but in general it will be:

cp -pr $NETBOX_MEDIA_ROOT/* $NAUTOBOT_MEDIA_ROOT/*

Copy Custom Scripts and Reports

Similarly, the exact commands depend on your SCRIPTS_ROOT and REPORTS_ROOT settings in NetBox and your JOBS_ROOT in Nautobot, but in general they will be:

cp -pr $NETBOX_SCRIPTS_ROOT/* $NAUTOBOT_JOBS_ROOT/
cp -pr $NETBOX_REPORTS_ROOT/* $NAUTOBOT_JOBS_ROOT/

Update Scripts, Reports, and Plugins for Nautobot compatibility

Depending on the complexity of your scripts, reports, or plugins, and how tightly integrated with NetBox they were, it may be simple or complex to port them to be compatible with Nautobot, and we cannot possibly provide a generalized step-by-step guide that would cover all possibilities. One change that you will certainly have to make to even begin this process, however, is updating the Python module names for any modules that were being imported from NetBox:

  • circuits.* -> nautobot.circuits.*
  • dcim.* -> nautobot.dcim.*
  • extras.* -> nautobot.extras.*
  • ipam.* -> nautobot.ipam.*
  • netbox.* -> nautobot.core.*
  • tenancy.* -> nautobot.tenancy.*
  • utilities.* -> nautobot.utilities.*
  • virtualization.* -> nautobot.virtualization.*

Update Your other Integration Code

If you have developed any custom integrations or plugins you may need to update some of your calls. Please see the data model changes below for guidance.

Data Model Changes

The following backwards-incompatible changes have been made to the data model in Nautobot.

Status Fields

Tip

Status names are now lower-cased when setting the status field on CSV imports. The slug value is used for create/update of objects and for filtering in the API.

A new Status model has been added to represent the status field for many models. Each status has a human-readable name field (e.g. Active), and a slug field (e.g. active).

Display name

Several models such as device type and VLAN exposed a display_name property, which has now been renamed to display. In fact, there are several other instances, especially in the REST API, where the display_name field was used and as such, all instances have been renamed to display.

CSV Imports

When using CSV import to define a status field on imported objects, such as when importing Devices or Prefixes, the Status.slug field is used.

For example, the built-in Active status has a slug of active, so the active value would be used for import.

Default Choices

Because status fields are now stored in the database, they cannot have a default value, just like other similar objects like Device Roles or Device Types. In cases where status was not required to be set because it would use the default value, you must now provide a status yourself.

Note

For consistency in the API, the slug value of a status is used when creating or updating an object.

Choices in Code

All *StatusChoices enums used for populated status field choices (such as nautobot.dcim.choices.DeviceStatusChoices) are deprecated. Any code you have that is leveraging these will now result in an error when performing lookups on objects with status fields.

Anywhere you have code like this:

from dcim.choices import DeviceStatusChoices
from dcim.models import Device

Device.objects.filter(status=DeviceStatusChoices.STATUS_PLANNED)

Update it to this:

from nautobot.extras.models import Status
from nautobot.dcim.models import Device

Device.objects.filter(status=Status.objects.get(slug="planned"))

UUID Primary Database Keys

Tip

Primary key (aka ID) fields are no longer auto-incrementing integers and are now randomly-generated UUIDs.

Database keys are now defined as randomly-generated Universally Unique Identifiers (UUIDs) instead of integers, protecting against certain classes of data-traversal attacks.

Merge of UserConfig data into User model

There is no longer a distinct UserConfig model; instead, user configuration and preferences are stored directly on the User model under the key config_data.

Custom Fields

Tip

You can no longer rename or change the type of a custom field.

Custom Fields have been overhauled for asserting data integrity and improving user experience.

  • Custom Fields can no longer be renamed or have their type changed after they have been created.
  • Choices for Custom Fields are now stored as discrete CustomFieldChoice database objects. Choices that are in active use cannot be deleted.

IPAM Network Field Types

Tip

Nautobot 1.2 and later supports most of the same filter-based network membership queries as NetBox. See below and the filtering documentation for more details. (Prior to Nautobot 1.2, IPAM network objects only supported model-manager-based methods for network membership filtering.)

All IPAM objects with network field types (ipam.Aggregate, ipam.IPAddress, and ipam.Prefix) are no longer hard-coded to use PostgreSQL-only inet or cidr field types and are now using a custom implementation leveraging SQL-standard varbinary field types.

Technical Details

Below is a summary of the underlying technical changes to network fields. These will be explained in more detail in the following sections.

  • For IPAddress, the address field was exploded out to host, broadcast, and prefix_length fields; address was converted into a computed field.
  • For Aggregate and Prefix objects, the prefix field was exploded out to network, broadcast, and prefix_length fields; prefix was converted into a computed field.
  • The host, network, and broadcast fields are now of a varbinary database type, which is represented as a packed binary integer (for example, the host 1.1.1.1 is packed as b"\x01\x01\x01\x01")
  • Network membership queries are accomplished by triangulating the "position" of an address using the IP, broadcast, and prefix length of the source and target addresses.

Note

You should never have to worry about the binary nature of how the network fields are stored in the database! The Django database ORM takes care of it all!

Changes to IPAddress

The following fields have changed when working with ipam.IPAddress objects:

address is now a computed field

This field is computed from {host}/{prefix_length} and is represented as a netaddr.IPNetwork object.

>>> ip = IPAddress(address="1.1.1.1/30")
>>> ip.address
IPNetwork('1.1.1.1/30')

While this field is now a virtual field, it can still be used in many ways.

It can be used to create objects:

>>> ip = IPAddress.objects.create(address="1.1.1.1/30")

It can be used in .get() and .filter() lookups where address is the primary argument:

>>> IPAddress.objects.get(address="1.1.1.1/30")
IPNetwork('1.1.1.1/30')
>>> IPAddress.objects.filter(address="1.1.1.1/30")
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>]>

Note

If you use a prefix_length other than /32 (IPv4) or /128 (IPv6) it must be included in your lookups

This field cannot be used in nested filter expressions:

>>> Device.objects.filter(primary_ip4__address="1.1.1.1")
django.core.exceptions.FieldError: Related Field got invalid lookup: address
host contains the IP address

The IP (host) component of the address is now stored in the host field.

>>> ip.host
'1.1.1.1'

This field can be used in nested filter expressions, for example:

>>> Device.objects.filter(primary_ip4__host="1.1.1.1")
IPAddress prefix_length contains the prefix length

This is an integer, such as 30 for /30.

>>> ip.prefix_length
30

For IP addresses with a prefix length other than a host prefix, you will need to filter using host and prefix_length fields for greater accuracy.

For example, if you have multiple IPAddress objects with the same host value but different prefix_length:

>>> IPAddress.objects.create(address="1.1.1.1/32")
<IPAddress: 1.1.1.1/32>
>>> IPAddress.objects.filter(host="1.1.1.1")
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>, <IPAddress: 1.1.1.1/32>]>
>>> IPAddress.objects.filter(host="1.1.1.1", prefix_length=30)
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>]>
IPAddress broadcast contains the broadcast address

If the prefix length is that of a host prefix (e.g. /32), broadcast will be the same as the host :

>>> IPAddress.objects.get(address="1.1.1.1/32").broadcast
'1.1.1.1'

If the prefix length is any larger (e.g. /24), broadcast will be that of the containing network for that prefix length (e.g. 1.1.1.255):

>>> IPAddress.objects.create(address="1.1.1.1/24").broadcast
'1.1.1.255'

Note

This field is largely for internal use only for facilitating network membership queries and it is not recommend that you use it for filtering.

Changes to Aggregate and Prefix

The following fields have changed when working with ipam.Aggregate and ipam.Prefix objects. These objects share the same field changes.

For these examples we will be using Prefix objects, but they apply just as equally to Aggregate objects.

prefix is now a computed field

This field is computed from {network}/{prefix_length} and is represented as a netaddr.IPNetwork object.

While this field is now a virtual field, it can still be used in many ways.

It can be used to create objects:

>>> net = Prefix.objects.create(prefix="1.1.1.0/24")

It can be used in .get() and .filter() lookups where prefix is the primary argument:

>>> Prefix.objects.get(prefix="1.1.1.0/24")
<Prefix: 1.1.1.0/24>
>>> Prefix.objects.filter(prefix="1.1.1.0/24")
<PrefixQuerySet [<Prefix: 1.1.1.0/24>]>
network contains the network address

The network component of the address is now stored in the network field.

>>> net.network
'1.1.1.0'
Aggregate/Prefix prefix_length contains the prefix length

This is an integer, such as 24 for /24.

>>> net.prefix_length
24

It's highly likely that you will have multiple objects with the same network address but varying prefix lengths, so you will need to filter using network and prefix_length fields for greater accuracy.

For example, if you have multiple Prefix objects with the same network value but different prefix_length:

>>> Prefix.objects.create(prefix="1.1.1.0/25")
<Prefix: 1.1.1.0/25>
>>> Prefix.objects.filter(network="1.1.1.0")
<PrefixQuerySet [<Prefix: 1.1.1.0/24>, <Prefix: 1.1.1.0/25>]>
>>> Prefix.objects.filter(network="1.1.1.0", prefix_length=25)
<PrefixQuerySet [<Prefix: 1.1.1.0/25>]>
Aggregate/Prefix broadcast contains the broadcast address

The broadcast will be derived from the prefix_length and will be that of the last network address for that prefix length (e.g. 1.1.1.255):

>>> Prefix.objects.get(prefix="1.1.1.0/24").broadcast
'1.1.1.255'

Note

This field is largely for internal use only for facilitating network membership queries and it is not recommend that you use it for filtering.

Membership Lookups

Nautobot 1.0.x and 1.1.x did not support the custom lookup expressions that NetBox supported for membership queries on IPAM objects (such as Prefix.objects.filter(prefix__net_contained="10.0.0.0/24")), but instead provided an alternate approach using model manager methods (such as Prefix.objects.net_contained("10.0.0.0/24")).

In Nautobot 1.2.0 and later, both model manager methods and custom lookup expressions are supported for this purpose, but the latter are now preferred for most use cases and are generally equivalent to their counterparts in NetBox.

Note

Nautobot did not mimic the support of non-subnets for the net_in query to avoid mistakes and confusion caused by an IP address being mistaken for a /32 as an example.

net_mask_length

Returns target addresses matching the source address prefix length.

Note

The NetBox filter net_mask_length should use the prefix_length field for filtering.

NetBox:

IPAddress.objects.filter(address__net_mask_length=value)
# or
Prefix.objects.filter(prefix__net_mask_length=value)

Nautobot:

IPAddress.objects.filter(prefix_length=value)
# or
Prefix.objects.filter(prefix_length=value)

REST API Changes

The following backwards-incompatible changes have been made to the REST API in Nautobot.

Display field

In several endpoints such as device type and VLAN, a display_name field is used to expose a human friendly string value for the object. This field has been renamed to display and has been standardized across all model API endpoints.

Custom Field Choices

Custom field choices are exposed in Nautobot at a dedicated endpoint: /api/extras/custom-field-choices/. This replaces the choices field on on the CustomField model and the subsequent endpoint: /api/extras/custom-fields/