Migrating from NetBox - Nautobot Documentation

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 App, `nautobot-netbox-importer`, that can be used to import a NetBox database dump file into Nautobot. For full details, refer to the App's own documentation, but here is a brief overview:

1. Export your NetBox database to a JSON file.
2. Install the importer App.
3. Enable the importer App.
4. Run the App'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.core.*`
- `virtualization.* -> nautobot.virtualization.*`

Changed in version 2.0.0

The equivalent of NetBox's `utilities` module moved from `nautobot.utilities` to `nautobot.core`.

### 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

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`), a `color`, a `description`, and a set of `content_types` that it can be applied to (for example, `Device` and `Location`).

### 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`.

#### 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.

#### 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(name="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.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` and `mask_length` fields; `address` was converted into a computed field.
- For `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 `host` of the `IPAddress` and the `network` and `broadcast` of the `Prefix`.

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}/{mask_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 `mask_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 `mask_length` contains the prefix length

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

    >>> ip.mask_length
    30

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

For example, if you have multiple `IPAddress` objects with the same `host` value but different `mask_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", mask_length=30)
    <IPAddressQuerySet [<IPAddress: 1.1.1.1/30>]>

#### Changes to `Aggregate` and `Prefix`

The following fields have changed when working with `ipam.Aggregate` and `ipam.Prefix` objects.

##### `Aggregate` model merged into `Prefix`

The Aggregate model has been removed in Nautobot and its functionality was moved to the Prefix model. The `rir` and `tenant` fields were directly copied over to Prefix, the `date_added` field was renamed to `date_allocated` and changed to a DateTimeField in Prefix.

##### `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'

##### 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>]>

##### 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(mask_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/`