Repository: nautobot/nautobot-app-nornir (language: python)

Issue:
Cookie initialy baked by NetworkToCode Cookie Drift Manager Tool
Template:

```
{
    "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git",
    "dir": "nautobot-app",
    "ref": "develop",
    "path": null
}
```

Cookie:

```
{
    "remote": "https://github.com/nautobot/nautobot-plugin-nornir.git",
    "path": "/opt/ntc/drift-manager/outputs/nautobot-plugin-nornir",
    "repository_path": "/opt/ntc/drift-manager/outputs/nautobot-plugin-nornir",
    "dir": "",
    "branch_prefix": "drift-manager",
    "context": {
        "codeowner_github_usernames": "@smith-ntc",
        "full_name": "Network to Code, LLC",
        "email": "info@networktocode.com",
        "github_org": "nautobot",
        "plugin_name": "nautobot_plugin_nornir",
        "verbose_name": "Nautobot Nornir Plugin",
        "plugin_slug": "nautobot-plugin-nornir",
        "project_slug": "nautobot-plugin-nornir",
        "repo_url": "https://github.com/nautobot/nautobot-plugin-nornir",
        "base_url": "plugin-nornir",
        "min_nautobot_version": "2.0.0",
        "max_nautobot_version": "2.9999",
        "camel_name": "NautobotPluginNornir",
        "project_short_description": "Nautobot Nornir Plugin",
        "model_class_name": "None",
        "open_source_license": "Apache-2.0",
        "docs_base_url": "https://docs.nautobot.com",
        "docs_app_url": "https://docs.nautobot.com/projects/plugin-nornir/en/latest",
        "_template": "https://github.com/nautobot/cookiecutter-nautobot-app.git",
        "_output_dir": "/opt/ntc/drift-manager/outputs",
        "_repo_dir": "/opt/ntc/drift-manager/outputs/.cookiecutters/cookiecutter-nautobot-app/nautobot-app",
        "_checkout": "develop"
    },
    "base_branch": "develop",
    "remote_name": "origin",
    "pull_request_strategy": "PullRequestStrategy.UPDATE_OR_CREATE",
    "post_actions": [
        "PostAction.BLACK"
    ],
    "baked_commit_ref": "",
    "draft": true
}
```

CLI Arguments:

```
{
    "cookie_dir": "",
    "input": false,
    "json_filename": "setup-cookie-nornir.json",
    "output_dir": "./outputs",
    "push": true,
    "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git",
    "template_dir": "nautobot-app",
    "template_ref": "develop",
    "pull_request": "update-or-create",
    "post_action": [
        "black"
    ],
    "disable_post_actions": false,
    "draft": true
}
```


Discussion / hints:

Fix patch:
diff --git a/.cookiecutter.json b/.cookiecutter.json
index 06717a2..7c9b129 100644
--- a/.cookiecutter.json
+++ b/.cookiecutter.json
@@ -1,26 +1,35 @@
 {
-  "cookiecutter": {
-    "codeowner_github_usernames": "@smith-ntc",
-    "full_name": "Network to Code, LLC",
-    "email": "info@networktocode.com",
-    "github_org": "nautobot",
-    "plugin_name": "nautobot_plugin_nornir",
-    "verbose_name": "Nautobot Nornir Plugin",
-    "plugin_slug": "nautobot-plugin-nornir",
-    "project_slug": "nautobot-plugin-nornir",
-    "repo_url": "https://github.com/nautobot/nautobot-plugin-nornir",
-    "base_url": "plugin-nornir",
-    "min_nautobot_version": "1.4.0",
-    "max_nautobot_version": "1.9999",
-    "nautobot_version": "latest",
-    "camel_name": "NautobotPluginNornir",
-    "project_short_description": "Nautobot Nornir Plugin",
-    "version": "0.1.0",
-    "model_class_name": "None",
-    "open_source_license": "Apache-2.0",
-    "docs_base_url": "https://docs.nautobot.com",
-    "docs_app_url": "https://docs.nautobot.com/projects/plugin-nornir/en/latest",
-    "_template": "/opt/ntc/networktocode-llc/cookiecutter-ntc/nautobot-plugin",
-    "_output_dir": "/opt/ntc/baked"
-  }
+    "cookiecutter": {
+        "codeowner_github_usernames": "-* @itdependsnetworks\n-* @jeffkala",
+        "full_name": "Network to Code, LLC",
+        "email": "info@networktocode.com",
+        "github_org": "nautobot",
+        "app_name": "nautobot_plugin_nornir",
+        "verbose_name": "Nautobot Nornir Plugin",
+        "app_slug": "nautobot-plugin-nornir",
+        "project_slug": "nautobot-plugin-nornir",
+        "repo_url": "https://github.com/nautobot/nautobot-plugin-nornir",
+        "base_url": "plugin-nornir",
+        "min_nautobot_version": "2.0.0",
+        "max_nautobot_version": "2.9999",
+        "camel_name": "NautobotPluginNornir",
+        "project_short_description": "Nautobot App that provides a shim layer to simplify using Nornir within other Nautobot Apps and Nautobot Jobs",
+        "model_class_name": "None",
+        "open_source_license": "Apache-2.0",
+        "docs_base_url": "https://docs.nautobot.com",
+        "docs_app_url": "https://docs.nautobot.com/projects/plugin-nornir/en/latest",
+        "_drift_manager": {
+            "template": "https://github.com/nautobot/cookiecutter-nautobot-app.git",
+            "template_dir": "nautobot-app",
+            "template_ref": "develop",
+            "cookie_dir": "",
+            "branch_prefix": "drift-manager",
+            "pull_request_strategy": "update-or-create",
+            "post_actions": [
+                "black"
+            ],
+            "draft": true,
+            "baked_commit_ref": "7cd62373450b4459a5fa3397a2d27214f7217a9c"
+        }
+    }
 }
diff --git a/.flake8 b/.flake8
index 888023f..c9f5e84 100644
--- a/.flake8
+++ b/.flake8
@@ -1,7 +1,7 @@
 [flake8]
-# E501: Line length is enforced by Black, so flake8 doesn't need to check it
-# W503: Black disagrees with this rule, as does PEP 8; Black wins
-ignore = E501, W503
+ignore =
+  E501,  # Line length is enforced by Black, so flake8 doesn't need to check it
+  W503   # Black disagrees with this rule, as does PEP 8; Black wins
 exclude = 
   migrations,
   __pycache__,
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 1f0c4af..37fd21f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -4,10 +4,17 @@ about: Report a reproducible bug in the current release of nautobot-plugin-norni
 ---
 
 ### Environment
-* Python version:  <!-- Example: 3.11.5 -->
-* Nautobot version:  <!-- Example: 1.0.1 -->
+* Python version:  <!-- Example: 3.11.4 -->
+* Nautobot version:  <!-- Example: 2.0.0 -->
 * nautobot-plugin-nornir version:  <!-- Example: 1.0.0 -->
 
+<!-- What did you expect to happen? -->
+### Expected Behavior
+
+
+<!-- What happened instead? -->
+### Observed Behavior
+
 <!--
     Describe in detail the exact steps that someone else can take to reproduce
     this bug using the current release.
@@ -16,10 +23,3 @@ about: Report a reproducible bug in the current release of nautobot-plugin-norni
 1.
 2.
 3.
-
-<!-- What did you expect to happen? -->
-### Expected Behavior
-
-
-<!-- What happened instead? -->
-### Observed Behavior
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 44a32c1..34f1cd1 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,11 +1,11 @@
 ---
 name: ✨ Feature Request
-about: Propose a new feature or enhancement for nautobot-plugin-nornir
+about: Propose a new feature or enhancement
+
 ---
 
 ### Environment
-* Python version:  <!-- Example: 3.11.5 -->
-* Nautobot version:  <!-- Example: 1.0.1 -->
+* Nautobot version:  <!-- Example: 2.0.0 -->
 * nautobot-plugin-nornir version:  <!-- Example: 1.0.0 -->
 
 <!--
diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
new file mode 100644
index 0000000..3950227
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -0,0 +1,35 @@
+<!--
+    Thank you for your interest in contributing to Nautobot! Please note
+    that our contribution policy recommends that a feature request or bug
+    report be opened for approval prior to filing a pull request. This
+    helps avoid wasting time and effort on something that we might not
+    be able to accept.
+
+    Please indicate the relevant feature request or bug report below.
+-->
+
+# Closes: #<ISSUE NUMBER GOES HERE>
+
+## What's Changed
+
+<!--
+    Please include:
+    - A summary of the proposed changes
+    - A sectioned breakdown for larger features under ## subheadings
+    - Screenshots, example payloads where relevant:
+      - Before/After for bugfixes
+      - Using a new feature
+-->
+
+## To Do
+
+<!--
+    Please feel free to update todos to keep track of your own notes for WIP PRs.
+-->
+- [ ] Explanation of Change(s)
+- [ ] Added change log fragment(s) (for more information see [the documentation](https://docs.nautobot.com/projects/core/en/stable/development/#creating-changelog-fragments))
+- [ ] Attached Screenshots, Payload Example
+- [ ] Unit, Integration Tests
+- [ ] Documentation Updates (when adding/changing features)
+- [ ] Example Plugin Updates (when adding/changing features)
+- [ ] Outline Remaining Work, Constraints from Design
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5c6ffa6..13a5610 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,56 +17,67 @@ env:
 
 jobs:
   black:
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     env:
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_LOCAL: "True"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Setup environment"
         uses: "networktocode/gh-action-setup-poetry-environment@v4"
       - name: "Linting: black"
         run: "poetry run invoke black"
   bandit:
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     env:
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_LOCAL: "True"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Setup environment"
         uses: "networktocode/gh-action-setup-poetry-environment@v4"
       - name: "Linting: bandit"
         run: "poetry run invoke bandit"
   pydocstyle:
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     env:
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_LOCAL: "True"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Setup environment"
         uses: "networktocode/gh-action-setup-poetry-environment@v4"
       - name: "Linting: pydocstyle"
         run: "poetry run invoke pydocstyle"
   flake8:
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     env:
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_LOCAL: "True"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Setup environment"
         uses: "networktocode/gh-action-setup-poetry-environment@v4"
       - name: "Linting: flake8"
         run: "poetry run invoke flake8"
+  poetry:
+    runs-on: "ubuntu-22.04"
+    env:
+      INVOKE_NAUTOBOT_PLUGIN_NORNIR_LOCAL: "True"
+    steps:
+      - name: "Check out repository code"
+        uses: "actions/checkout@v4"
+      - name: "Setup environment"
+        uses: "networktocode/gh-action-setup-poetry-environment@v4"
+      - name: "Checking: poetry lock file"
+        run: "poetry run invoke lock --check"
   yamllint:
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     env:
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_LOCAL: "True"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Setup environment"
         uses: "networktocode/gh-action-setup-poetry-environment@v4"
       - name: "Linting: yamllint"
@@ -76,9 +87,10 @@ jobs:
       - "bandit"
       - "pydocstyle"
       - "flake8"
+      - "poetry"
       - "yamllint"
       - "black"
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     strategy:
       fail-fast: true
       matrix:
@@ -89,14 +101,14 @@ jobs:
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Setup environment"
         uses: "networktocode/gh-action-setup-poetry-environment@v4"
       - name: "Set up Docker Buildx"
         id: "buildx"
-        uses: "docker/setup-buildx-action@v1"
+        uses: "docker/setup-buildx-action@v3"
       - name: "Build"
-        uses: "docker/build-push-action@v2"
+        uses: "docker/build-push-action@v5"
         with:
           builder: "${{ steps.buildx.outputs.name }}"
           context: "./"
@@ -113,43 +125,80 @@ jobs:
         run: "cp development/creds.example.env development/creds.env"
       - name: "Linting: pylint"
         run: "poetry run invoke pylint"
+  check-migrations:
+    needs:
+      - "bandit"
+      - "pydocstyle"
+      - "flake8"
+      - "poetry"
+      - "yamllint"
+      - "black"
+    runs-on: "ubuntu-22.04"
+    strategy:
+      fail-fast: true
+      matrix:
+        python-version: ["3.11"]
+        nautobot-version: ["2.0.0"]
+    env:
+      INVOKE_NAUTOBOT_PLUGIN_NORNIR_PYTHON_VER: "${{ matrix.python-version }}"
+      INVOKE_NAUTOBOT_PLUGIN_NORNIR_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
+    steps:
+      - name: "Check out repository code"
+        uses: "actions/checkout@v4"
+      - name: "Setup environment"
+        uses: "networktocode/gh-action-setup-poetry-environment@v4"
+      - name: "Set up Docker Buildx"
+        id: "buildx"
+        uses: "docker/setup-buildx-action@v3"
+      - name: "Build"
+        uses: "docker/build-push-action@v5"
+        with:
+          builder: "${{ steps.buildx.outputs.name }}"
+          context: "./"
+          push: false
+          load: true
+          tags: "${{ env.PLUGIN_NAME }}/nautobot:${{ matrix.nautobot-version }}-py${{ matrix.python-version }}"
+          file: "./development/Dockerfile"
+          cache-from: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}"
+          cache-to: "type=gha,scope=${{ matrix.nautobot-version }}-py${{ matrix.python-version }}"
+          build-args: |
+            NAUTOBOT_VER=${{ matrix.nautobot-version }}
+            PYTHON_VER=${{ matrix.python-version }}
+      - name: "Copy credentials"
+        run: "cp development/creds.example.env development/creds.env"
+      - name: "Checking: migrations"
+        run: "poetry run invoke check-migrations"
   unittest:
     needs:
       - "pylint"
+      - "check-migrations"
     strategy:
       fail-fast: true
       matrix:
-        python-version: ["3.8"]
-        db-backend: ["postgresql", "mysql"]
-        nautobot-version: ["2.0.0"]
-        # The include is a method to limit the amount of jobs ran. This essentially
-        # means that in addition to standard postgres and stable, also the lowest
-        # supported version and with mysql
+        python-version: ["3.8", "3.11"]
+        db-backend: ["postgresql"]
+        nautobot-version: ["stable"]
         include:
           - python-version: "3.11"
             db-backend: "postgresql"
             nautobot-version: "2.0.0"
-          - python-version: "3.11"
-            db-backend: "postgresql"
-            nautobot-version: "stable"
           - python-version: "3.11"
             db-backend: "mysql"
             nautobot-version: "stable"
-
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     env:
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_PYTHON_VER: "${{ matrix.python-version }}"
       INVOKE_NAUTOBOT_PLUGIN_NORNIR_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Setup environment"
         uses: "networktocode/gh-action-setup-poetry-environment@v4"
       - name: "Set up Docker Buildx"
         id: "buildx"
-        uses: "docker/setup-buildx-action@v1"
+        uses: "docker/setup-buildx-action@v3"
       - name: "Build"
-        uses: "docker/build-push-action@v2"
+        uses: "docker/build-push-action@v5"
         with:
           builder: "${{ steps.buildx.outputs.name }}"
           context: "./"
@@ -173,13 +222,13 @@ jobs:
     needs:
       - "unittest"
     name: "Publish to GitHub"
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     if: "startsWith(github.ref, 'refs/tags/v')"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Set up Python"
-        uses: "actions/setup-python@v2"
+        uses: "actions/setup-python@v4"
         with:
           python-version: "3.11"
       - name: "Install Python Packages"
@@ -193,7 +242,7 @@ jobs:
       - name: "Upload binaries to release"
         uses: "svenstaro/upload-release-action@v2"
         with:
-          repo_token: "${{ secrets.GH_NAUTOBOT_BOT_TOKEN }}"
+          repo_token: "${{ secrets.NTC_GITHUB_TOKEN }}"  # use GH_NAUTOBOT_BOT_TOKEN for Nautobot Org repos.
           file: "dist/*"
           tag: "${{ github.ref }}"
           overwrite: true
@@ -202,13 +251,13 @@ jobs:
     needs:
       - "unittest"
     name: "Push Package to PyPI"
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     if: "startsWith(github.ref, 'refs/tags/v')"
     steps:
       - name: "Check out repository code"
-        uses: "actions/checkout@v2"
+        uses: "actions/checkout@v4"
       - name: "Set up Python"
-        uses: "actions/setup-python@v2"
+        uses: "actions/setup-python@v4"
         with:
           python-version: "3.11"
       - name: "Install Python Packages"
@@ -228,9 +277,9 @@ jobs:
     needs:
       - "publish_gh"
       - "publish_pypi"
-    runs-on: "ubuntu-20.04"
+    runs-on: "ubuntu-22.04"
     env:
-      SLACK_WEBHOOK_URL: "${{ '{{ secrets.SLACK_WEBHOOK_URL }}' }}"
+      SLACK_WEBHOOK_URL: "${{ secrets.SLACK_WEBHOOK_URL }}"
       SLACK_MESSAGE: >-
         *NOTIFICATION: NEW-RELEASE-PUBLISHED*\n
         Repository: <${{ github.server_url }}/${{ github.repository }}|${{ github.repository }}>\n
@@ -240,11 +289,12 @@ jobs:
       - name: "Send a notification to Slack"
         # ENVs cannot be used directly in job.if. This is a workaround to check
         # if SLACK_WEBHOOK_URL is present.
-        if: "${{ env.SLACK_WEBHOOK_URL != '' }}"
-        uses: "slackapi/slack-github-action@v1.17.0"
+        if: "env.SLACK_WEBHOOK_URL != ''"
+        uses: "slackapi/slack-github-action@v1"
         with:
           payload: |
             {
+              "text": "${{ env.SLACK_MESSAGE }}",
               "blocks": [
                 {
                   "type": "section",
diff --git a/.github/workflows/rebake.yml b/.github/workflows/rebake.yml
new file mode 100644
index 0000000..b468884
--- /dev/null
+++ b/.github/workflows/rebake.yml
@@ -0,0 +1,118 @@
+---
+name: "Rebake Cookie"
+on:  # yamllint disable-line rule:truthy
+  workflow_call:
+    inputs:
+      cookie:
+        description: "The cookie to rebake"
+        type: "string"
+        default: ""
+      draft:
+        description: "Whether to create the pull request as a draft"
+        type: "string"
+        default: ""
+      pull-request:
+        description: "The pull request strategy"
+        type: "string"
+        default: ""
+      template:
+        description: "The template repository URL"
+        type: "string"
+        default: ""
+      template-dir:
+        description: "The directory within the template repository to use as the template"
+        type: "string"
+        default: ""
+      template-ref:
+        description: "The branch or tag to use for the template"
+        type: "string"
+        default: ""
+      drift-manager-tag:
+        description: "The drift manager Docker image tag to use"
+        type: "string"
+        default: "prod"
+  workflow_dispatch:
+    inputs:
+      cookie:
+        description: "The cookie to rebake"
+        type: "string"
+        default: ""
+      draft:
+        description: "Whether to create the pull request as a draft"
+        type: "string"
+        default: ""
+      pull-request:
+        description: "The pull request strategy"
+        type: "string"
+        default: ""
+      template:
+        description: "The template repository URL"
+        type: "string"
+        default: ""
+      template-dir:
+        description: "The directory within the template repository to use as the template"
+        type: "string"
+        default: ""
+      template-ref:
+        description: "The branch or tag to use for the template"
+        type: "string"
+        default: ""
+      drift-manager-tag:
+        description: "The drift manager Docker image tag to use"
+        type: "string"
+        default: "prod"
+jobs:
+  rebake:
+    runs-on: "ubuntu-22.04"
+    permissions:
+      actions: "write"
+      contents: "write"
+      packages: "read"
+      pull-requests: "write"
+    container: "ghcr.io/nautobot/cookiecutter-nautobot-app-drift-manager/prod:${{ github.event.inputs.drift-manager-tag }}"
+    env:
+      GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+    steps:
+      - name: "Configure Rebake Arguments"
+        id: "config"
+        shell: "bash"
+        run: |
+          ARGS='--push'
+
+          if [[ '${{ github.event.inputs.draft }}' == 'true' ]]; then
+            ARGS="$ARGS --draft"
+          elif [[ '${{ github.event.inputs.draft }}' == 'false' ]]; then
+            ARGS="$ARGS --no-draft"
+          elif [[ '${{ github.event.inputs.draft }}' == '' ]]; then
+            echo "Using repo default value for --draft"
+          else
+            echo "ERROR: Invalid value for draft: '${{ github.event.inputs.draft }}'"
+            exit 1
+          fi
+
+          if [[ '${{ github.event.inputs.pull-request }}' != '' ]]; then
+            ARGS="$ARGS --pull-request='${{ github.event.inputs.pull-request }}'"
+          fi
+
+          if [[ '${{ github.event.inputs.template }}' != '' ]]; then
+            ARGS="$ARGS --template='${{ github.event.inputs.template }}'"
+          fi
+
+          if [[ '${{ github.event.inputs.template-dir }}' != '' ]]; then
+            ARGS="$ARGS --template-dir='${{ github.event.inputs.template-dir }}'"
+          fi
+
+          if [[ '${{ github.event.inputs.template-ref }}' != '' ]]; then
+            ARGS="$ARGS --template-ref='${{ github.event.inputs.template-ref }}'"
+          fi
+
+          if [[ '${{ github.event.inputs.cookie }}' == '' ]]; then
+            ARGS="$ARGS '${{ github.repositoryUrl }}'"
+          else
+            ARGS="$ARGS '${{ github.event.inputs.cookie }}'"
+          fi
+
+          echo "args=$ARGS" >> $GITHUB_OUTPUT
+      - name: "Rebake"
+        run: |
+          python -m ntc_cookie_drift_manager rebake ${{ steps.config.outputs.args }}
diff --git a/.gitignore b/.gitignore
index ae46c7d..51186e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,3 @@
-# Specific ignores
-nautobot_backup.dump
-.creds.env
-creds.env
-nautobot_golden_config/transposer.py
-docker-compose.override.yml
-packages/
-
 # Ansible Retry Files
 *.retry
 
@@ -200,9 +192,6 @@ $RECYCLE.BIN/
 # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
 
-# Jetbrains IDE configs
-.idea/
-
 # User-specific stuff
 .idea/**/workspace.xml
 .idea/**/tasks.xml
@@ -306,6 +295,15 @@ fabric.properties
 .vscode/*
 *.code-workspace
 
+# Rando
+creds.env
+development/*.txt
+
+# Invoke overrides
+invoke.yml
 
-# MKDocs Build
-nautobot_plugin_nornir/static/nautobot_plugin_nornir/docs
\ No newline at end of file
+# Docs
+public
+/compose.yaml
+/dump.sql
+/nautobot_plugin_nornir/static/nautobot_plugin_nornir/docs
diff --git a/.yamllint.yml b/.yamllint.yml
index b49e490..8cc3e9a 100644
--- a/.yamllint.yml
+++ b/.yamllint.yml
@@ -10,3 +10,4 @@ rules:
     quote-type: "double"
 ignore: |
   .venv/
+  compose.yaml
diff --git a/LICENSE b/LICENSE
index 087f92f..d46cc97 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 Apache Software License 2.0
 
-Copyright (c) 2021, Network to Code, LLC
+Copyright (c) 2023, Network to Code, LLC
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff --git a/development/Dockerfile b/development/Dockerfile
index b89a369..4d10419 100644
--- a/development/Dockerfile
+++ b/development/Dockerfile
@@ -6,11 +6,11 @@
 # -------------------------------------------------------------------------------------
 # !!! USE CAUTION WHEN MODIFYING LINES BELOW
 
-# Accepts a desired Nautobot version as build argument, default to 2.0.0-rc.2
-ARG NAUTOBOT_VER="2.0.0-rc.3"
+# Accepts a desired Nautobot version as build argument, default to 2.0.0
+ARG NAUTOBOT_VER="2.0.0"
 
-# Accepts a desired Python version as build argument, default to 3.8
-ARG PYTHON_VER="3.8"
+# Accepts a desired Python version as build argument, default to 3.11
+ARG PYTHON_VER="3.11"
 
 # Retrieve published development image of Nautobot base which should include most CI dependencies
 FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER}
@@ -19,21 +19,21 @@ FROM ghcr.io/nautobot/nautobot-dev:${NAUTOBOT_VER}-py${PYTHON_VER}
 ARG NAUTOBOT_ROOT=/opt/nautobot
 
 ENV prometheus_multiproc_dir=/prom_cache
-ENV NAUTOBOT_ROOT ${NAUTOBOT_ROOT}
+ENV NAUTOBOT_ROOT=${NAUTOBOT_ROOT}
+ENV INVOKE_NAUTOBOT_PLUGIN_NORNIR_LOCAL=true
 
 # Install Poetry manually via its installer script;
 # We might be using an older version of Nautobot that includes an older version of Poetry
 # and CI and local development may have a newer version of Poetry
 # Since this is only used for development and we don't ship this container, pinning Poetry back is not expressly necessary
 # We also don't need virtual environments in container
-ARG POETRY_VERSION=1.5.1
-RUN curl -sSL https://install.python-poetry.org | python3 - && \
+RUN which poetry || curl -sSL https://install.python-poetry.org | python3 - && \
   poetry config virtualenvs.create false
 
 # !!! USE CAUTION WHEN MODIFYING LINES ABOVE
 # -------------------------------------------------------------------------------------
 # App-specifc system build/test dependencies.
-# 
+#
 # Example: LDAP requires `libldap2-dev` to be apt-installed before the Python package.
 # -------------------------------------------------------------------------------------
 # --> Start safe to modify section
@@ -69,11 +69,13 @@ RUN sort poetry_freeze_base.txt poetry_freeze_all.txt | uniq -u > poetry_freeze_
 
 # Install all local project as editable, constrained on Nautobot version, to get any additional
 #   direct dependencies of the app
-RUN pip install -c constraints.txt -e .
+RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \
+    pip install -c constraints.txt -e .[all]
 
 # Install any dev dependencies frozen from Poetry
 # Can be improved in Poetry 1.2 which allows `poetry install --only dev`
-RUN pip install -c constraints.txt -r poetry_freeze_dev.txt
+RUN --mount=type=cache,target="/root/.cache/pip",sharing=locked \
+    pip install -c constraints.txt -r poetry_freeze_dev.txt
 
 COPY development/nautobot_config.py ${NAUTOBOT_ROOT}/nautobot_config.py
 # !!! USE CAUTION WHEN MODIFYING LINES ABOVE
diff --git a/development/development.env b/development/development.env
index 1945f95..54f0b87 100644
--- a/development/development.env
+++ b/development/development.env
@@ -7,8 +7,6 @@ NAUTOBOT_BANNER_TOP="Local"
 NAUTOBOT_CHANGELOG_RETENTION=0
 
 NAUTOBOT_DEBUG=True
-NAUTOBOT_DJANGO_EXTENSIONS_ENABLED=True
-NAUTOBOT_DJANGO_TOOLBAR_ENABLED=True
 NAUTOBOT_LOG_LEVEL=DEBUG
 NAUTOBOT_METRICS_ENABLED=True
 NAUTOBOT_NAPALM_TIMEOUT=5
diff --git a/development/docker-compose.base.yml b/development/docker-compose.base.yml
index 07b1421..847efaa 100644
--- a/development/docker-compose.base.yml
+++ b/development/docker-compose.base.yml
@@ -21,7 +21,9 @@ services:
         condition: "service_started"
       db:
         condition: "service_healthy"
-    <<: [*nautobot-build, *nautobot-base]
+    <<:
+      - *nautobot-base
+      - *nautobot-build
   worker:
     entrypoint:
       - "sh"
@@ -36,3 +38,13 @@ services:
       retries: 3
       test: ["CMD", "bash", "-c", "nautobot-server celery inspect ping --destination celery@$$HOSTNAME"]  ## $$ because of docker-compose
     <<: *nautobot-base
+  beat:
+    entrypoint:
+      - "sh"
+      - "-c"  # this is to evaluate the $NAUTOBOT_LOG_LEVEL from the env
+      - "nautobot-server celery beat -l $$NAUTOBOT_LOG_LEVEL"  ## $$ because of docker-compose
+    depends_on:
+      - "nautobot"
+    healthcheck:
+      disable: true
+    <<: *nautobot-base
diff --git a/development/docker-compose.dev.yml b/development/docker-compose.dev.yml
index 4fe673f..3554722 100644
--- a/development/docker-compose.dev.yml
+++ b/development/docker-compose.dev.yml
@@ -12,6 +12,8 @@ services:
     volumes:
       - "./nautobot_config.py:/opt/nautobot/nautobot_config.py"
       - "../:/source"
+    healthcheck:
+      test: ["CMD", "true"]  # Due to layering, disable: true won't work. Instead, change the test
   docs:
     entrypoint: "mkdocs serve -v -a 0.0.0.0:8080"
     ports:
@@ -30,6 +32,8 @@ services:
     volumes:
       - "./nautobot_config.py:/opt/nautobot/nautobot_config.py"
       - "../:/source"
+    healthcheck:
+      test: ["CMD", "true"]  # Due to layering, disable: true won't work. Instead, change the test
 # To expose postgres or redis to the host uncomment the following
 # postgres:
 #   ports:
diff --git a/development/docker-compose.mysql.yml b/development/docker-compose.mysql.yml
index c7fa6a1..062ada9 100644
--- a/development/docker-compose.mysql.yml
+++ b/development/docker-compose.mysql.yml
@@ -20,6 +20,7 @@ services:
     image: "mysql:8"
     command:
       - "--default-authentication-plugin=mysql_native_password"
+      - "--max_connections=1000"
     env_file:
       - "development.env"
       - "creds.env"
@@ -27,7 +28,12 @@ services:
     volumes:
       - "mysql_data:/var/lib/mysql"
     healthcheck:
-      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+      test:
+        - "CMD"
+        - "mysqladmin"
+        - "ping"
+        - "-h"
+        - "localhost"
       timeout: "20s"
       retries: 10
 volumes:
diff --git a/development/docker-compose.postgres.yml b/development/docker-compose.postgres.yml
index 55afdb7..12d1de3 100644
--- a/development/docker-compose.postgres.yml
+++ b/development/docker-compose.postgres.yml
@@ -7,11 +7,13 @@ services:
       - "NAUTOBOT_DB_ENGINE=django.db.backends.postgresql"
   db:
     image: "postgres:13-alpine"
+    command:
+      - "-c"
+      - "max_connections=200"
     env_file:
       - "development.env"
       - "creds.env"
     volumes:
-      # - "./nautobot.sql:/tmp/nautobot.sql"
       - "postgres_data:/var/lib/postgresql/data"
     healthcheck:
       test: "pg_isready --username=$$POSTGRES_USER --dbname=$$POSTGRES_DB"
diff --git a/development/nautobot_config.py b/development/nautobot_config.py
index dba0dd9..287e0b8 100644
--- a/development/nautobot_config.py
+++ b/development/nautobot_config.py
@@ -1,11 +1,25 @@
 """Nautobot development configuration file."""
-# pylint: disable=invalid-envvar-default
 import os
 import sys
 
 from nautobot.core.settings import *  # noqa: F403  # pylint: disable=wildcard-import,unused-wildcard-import
 from nautobot.core.settings_funcs import is_truthy, parse_redis_connection
 
+#
+# Debug
+#
+
+DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", False))
+_TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
+
+if DEBUG and not _TESTING:
+    DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: True}
+
+    if "debug_toolbar" not in INSTALLED_APPS:  # noqa: F405
+        INSTALLED_APPS.append("debug_toolbar")  # noqa: F405
+    if "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE:  # noqa: F405
+        MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")  # noqa: F405
+
 #
 # Misc. settings
 #
@@ -13,6 +27,9 @@
 ALLOWED_HOSTS = os.getenv("NAUTOBOT_ALLOWED_HOSTS", "").split(" ")
 SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "")
 
+#
+# Database
+#
 
 nautobot_db_engine = os.getenv("NAUTOBOT_DB_ENGINE", "django.db.backends.postgresql")
 default_db_settings = {
@@ -42,20 +59,28 @@
     DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"}
 
 #
-# Debug
+# Redis
 #
 
-DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", False))
-
-TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
+# The django-redis cache is used to establish concurrent locks using Redis.
+CACHES = {
+    "default": {
+        "BACKEND": "django_redis.cache.RedisCache",
+        "LOCATION": parse_redis_connection(redis_database=0),
+        "TIMEOUT": 300,
+        "OPTIONS": {
+            "CLIENT_CLASS": "django_redis.client.DefaultClient",
+        },
+    }
+}
 
-# Django Debug Toolbar
-DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda _request: DEBUG and not TESTING}
+# Redis Cacheops
+CACHEOPS_REDIS = parse_redis_connection(redis_database=1)
 
-if DEBUG and "debug_toolbar" not in INSTALLED_APPS:  # noqa: F405
-    INSTALLED_APPS.append("debug_toolbar")  # noqa: F405
-if DEBUG and "debug_toolbar.middleware.DebugToolbarMiddleware" not in MIDDLEWARE:  # noqa: F405
-    MIDDLEWARE.insert(0, "debug_toolbar.middleware.DebugToolbarMiddleware")  # noqa: F405
+#
+# Celery settings are not defined here because they can be overloaded with
+# environment variables. By default they use `CACHES["default"]["LOCATION"]`.
+#
 
 #
 # Logging
@@ -64,7 +89,7 @@
 LOG_LEVEL = "DEBUG" if DEBUG else "INFO"
 
 # Verbose logging during normal development operation, but quiet logging during unit test execution
-if not TESTING:
+if not _TESTING:
     LOGGING = {
         "version": 1,
         "disable_existing_loggers": False,
@@ -100,41 +125,14 @@
     }
 
 #
-# Redis
-#
-
-# The django-redis cache is used to establish concurrent locks using Redis. The
-# django-rq settings will use the same instance/database by default.
-#
-# This "default" server is now used by RQ_QUEUES.
-# >> See: nautobot.core.settings.RQ_QUEUES
-CACHES = {
-    "default": {
-        "BACKEND": "django_redis.cache.RedisCache",
-        "LOCATION": parse_redis_connection(redis_database=0),
-        "TIMEOUT": 300,
-        "OPTIONS": {
-            "CLIENT_CLASS": "django_redis.client.DefaultClient",
-        },
-    }
-}
-
-# RQ_QUEUES is not set here because it just uses the default that gets imported
-# up top via `from nautobot.core.settings import *`.
-
-# Redis Cacheops
-CACHEOPS_REDIS = parse_redis_connection(redis_database=1)
-
-#
-# Celery settings are not defined here because they can be overloaded with
-# environment variables. By default they use `CACHES["default"]["LOCATION"]`.
+# Apps
 #
 
-# Enable installed plugins. Add the name of each plugin to the list.
+# Enable installed Apps. Add the name of each App to the list.
 PLUGINS = ["nautobot_plugin_nornir"]
 
-# Plugins configuration settings. These settings are used by various plugins that the user may have installed.
-# Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
+# Apps configuration settings. These settings are used by various Apps that the user may have installed.
+# Each key in the dictionary is the name of an installed App and its value is a dictionary of settings.
 PLUGINS_CONFIG = {
     "nautobot_plugin_nornir": {
         "allowed_location_types": [],
diff --git a/docs/admin/compatibility_matrix.md b/docs/admin/compatibility_matrix.md
index d6d4398..4fbe76e 100644
--- a/docs/admin/compatibility_matrix.md
+++ b/docs/admin/compatibility_matrix.md
@@ -1,4 +1,3 @@
-
 # Deprecation Policy
 
 While that last supported version will not be strictly enforced--via the max_version setting, any issues with an updated Nautobot supported version in a minor release, will require a bug to be raised and a fix in Nautobot core to address, with no fixes expected in this plugin. This allows the Nautobot Nornir Plugin the ability to quickly take advantage of the latest features.
diff --git a/docs/admin/admin_install.md b/docs/admin/install.md
similarity index 100%
rename from docs/admin/admin_install.md
rename to docs/admin/install.md
diff --git a/docs/admin/release_notes/index.md b/docs/admin/release_notes/index.md
index 5793227..12cb516 100644
--- a/docs/admin/release_notes/index.md
+++ b/docs/admin/release_notes/index.md
@@ -1,3 +1,3 @@
 # Release Notes
 
-All the published release notes can be found via the navigation menu. All patch releases are included in the same minor release (e.g. `v1.1`) document.
+All the published release notes can be found via the navigation menu. All patch releases are included in the same minor release (e.g. `v1.2`) document.
diff --git a/docs/admin/release_notes/version_2.0.md b/docs/admin/release_notes/version_2.0.md
index c9226b2..2dcb918 100644
--- a/docs/admin/release_notes/version_2.0.md
+++ b/docs/admin/release_notes/version_2.0.md
@@ -10,6 +10,14 @@ This document describes all new features and changes in the release `2.0`. The f
 - Provided a mechanism to allow for Location Groupings to be configurable.
 
 
+## [v2.0.1] - 2024-03
+
+### Changed
+
+- [#120](https://github.com/nautobot/nautobot-plugin-nornir/issues/120) - Updated to use Cookiecutter from drift manager.
+- [#134](https://github.com/nautobot/nautobot-plugin-nornir/issues/134) - Changed to app vs plugin, first of many.
+- [#139](https://github.com/nautobot/nautobot-plugin-nornir/issues/139) - Changed mechanism to get secrets by caching them.
+
 ## [v2.0.0] - 2023-09
 
 ### Changed
diff --git a/docs/admin/admin_uninstall.md b/docs/admin/uninstall.md
similarity index 100%
rename from docs/admin/admin_uninstall.md
rename to docs/admin/uninstall.md
diff --git a/docs/admin/admin_upgrade.md b/docs/admin/upgrade.md
similarity index 100%
rename from docs/admin/admin_upgrade.md
rename to docs/admin/upgrade.md
diff --git a/docs/assets/extra.css b/docs/assets/extra.css
index 131cef7..dfe2e4b 100644
--- a/docs/assets/extra.css
+++ b/docs/assets/extra.css
@@ -18,6 +18,15 @@
     font-size: 0.7rem;
 }
 
+/*
+* The default max-width is 61rem which does not provide nearly enough space to present code examples or larger tables
+*/
+.md-grid {
+    margin-left: auto;
+    margin-right: auto;
+    max-width: 95%;
+}
+
 .md-tabs__link {
     font-size: 0.8rem;
 }
@@ -39,7 +48,7 @@
 }
 
 img.logo {
-    height: 100px;
+    height: 200px;
 }
 
 img.copyright-logo {
@@ -85,3 +94,68 @@ a.autorefs-external::after {
 a.autorefs-external:hover::after {
     background-color: var(--md-accent-fg-color);
 }
+
+
+/* Customization for mkdocs-version-annotations */
+:root {
+    /* Icon for "version-added" admonition: Material Design Icons "plus-box-outline" */
+    --md-admonition-icon--version-added: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 19V5H5v14h14m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-8 4h2v4h4v2h-4v4h-2v-4H7v-2h4V7Z"/></svg>');
+    /* Icon for "version-changed" admonition: Material Design Icons "delta" */
+    --md-admonition-icon--version-changed: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7.77 18.39 18H5.61L12 7.77M12 4 2 20h20"/></svg>');
+    /* Icon for "version-removed" admonition: Material Design Icons "minus-circle-outline" */
+    --md-admonition-icon--version-removed: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 20c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2M7 13h10v-2H7"/></svg>');
+}
+
+/* "version-added" admonition in green */
+.md-typeset .admonition.version-added,
+.md-typeset details.version-added {
+    border-color: rgb(0, 200, 83);
+}
+
+.md-typeset .version-added>.admonition-title,
+.md-typeset .version-added>summary {
+    background-color: rgba(0, 200, 83, .1);
+}
+
+.md-typeset .version-added>.admonition-title::before,
+.md-typeset .version-added>summary::before {
+    background-color: rgb(0, 200, 83);
+    -webkit-mask-image: var(--md-admonition-icon--version-added);
+    mask-image: var(--md-admonition-icon--version-added);
+}
+
+/* "version-changed" admonition in orange */
+.md-typeset .admonition.version-changed,
+.md-typeset details.version-changed {
+    border-color: rgb(255, 145, 0);
+}
+
+.md-typeset .version-changed>.admonition-title,
+.md-typeset .version-changed>summary {
+    background-color: rgba(255, 145, 0, .1);
+}
+
+.md-typeset .version-changed>.admonition-title::before,
+.md-typeset .version-changed>summary::before {
+    background-color: rgb(255, 145, 0);
+    -webkit-mask-image: var(--md-admonition-icon--version-changed);
+    mask-image: var(--md-admonition-icon--version-changed);
+}
+
+/* "version-removed" admonition in red */
+.md-typeset .admonition.version-removed,
+.md-typeset details.version-removed {
+    border-color: rgb(255, 82, 82);
+}
+
+.md-typeset .version-removed>.admonition-title,
+.md-typeset .version-removed>summary {
+    background-color: rgba(255, 82, 82, .1);
+}
+
+.md-typeset .version-removed>.admonition-title::before,
+.md-typeset .version-removed>summary::before {
+    background-color: rgb(255, 82, 82);
+    -webkit-mask-image: var(--md-admonition-icon--version-removed);
+    mask-image: var(--md-admonition-icon--version-removed);
+}
diff --git a/docs/assets/overrides/partials/copyright.html b/docs/assets/overrides/partials/copyright.html
index e0b77e5..b92cf5e 100644
--- a/docs/assets/overrides/partials/copyright.html
+++ b/docs/assets/overrides/partials/copyright.html
@@ -1,3 +1,4 @@
+
 <div class="md-copyright">
     {% if config.copyright %}
     <div class="md-copyright__highlight">
@@ -18,3 +19,4 @@
 
 <!-- RTD version flyout injected on live site -->
 <div id="readthedocs-embed-flyout"></div>
+
diff --git a/docs/dev/arch_decision.md b/docs/dev/arch_decision.md
new file mode 100644
index 0000000..7c21e70
--- /dev/null
+++ b/docs/dev/arch_decision.md
@@ -0,0 +1,3 @@
+# Architecture Decision Records
+
+The intention is to document deviations from a standard Model View Controller (MVC) design.
diff --git a/docs/dev/code_reference/index.md b/docs/dev/code_reference/index.md
index 6d6cb81..473f2c4 100644
--- a/docs/dev/code_reference/index.md
+++ b/docs/dev/code_reference/index.md
@@ -1,3 +1,3 @@
 # Code Reference
 
-Auto-generated code reference documentation from docstrings.
\ No newline at end of file
+Auto-generated code reference documentation from docstrings.
diff --git a/docs/dev/code_reference/package.md b/docs/dev/code_reference/package.md
new file mode 100644
index 0000000..2eb45b6
--- /dev/null
+++ b/docs/dev/code_reference/package.md
@@ -0,0 +1,1 @@
+::: nautobot_plugin_nornir
diff --git a/docs/dev/dev_contributing.md b/docs/dev/contributing.md
similarity index 100%
rename from docs/dev/dev_contributing.md
rename to docs/dev/contributing.md
diff --git a/docs/dev/dev_environment.md b/docs/dev/dev_environment.md
index 0406cfe..1b7d57f 100644
--- a/docs/dev/dev_environment.md
+++ b/docs/dev/dev_environment.md
@@ -1,11 +1,11 @@
 # Building Your Development Environment
 
-## Quickstart
+## Quickstart Guide
 
 The development environment can be used in two ways:
 
-1. `Recommended` All services are spun up using Docker and a local mount so you can develop locally, but Nautobot is spun up within the Docker container.
-2. With a local poetry environment if you wish to develop outside of Docker with the caveat of using external services provided by Docker for PostgresQL and Redis.
+1. **(Recommended)** All services, including Nautobot, are spun up using Docker containers and a volume mount so you can develop locally.
+2. With a local Poetry environment if you wish to develop outside of Docker, with the caveat of using external services provided by Docker for the database (PostgreSQL by default, MySQL optionally) and Redis services.
 
 This is a quick reference guide if you're already familiar with the development environment provided, which you can read more about later in this document.
 
@@ -13,8 +13,8 @@ This is a quick reference guide if you're already familiar with the development
 
 The [Invoke](http://www.pyinvoke.org/) library is used to provide some helper commands based on the environment. There are a few configuration parameters which can be passed to Invoke to override the default configuration:
 
-- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: latest)
-- `project_name`: the default docker compose project name (default: `nautobot_plugin_nornir`)
+- `nautobot_ver`: the version of Nautobot to use as a base for any built docker containers (default: 2.0.0)
+- `project_name`: the default docker compose project name (default: `nautobot-plugin-nornir`)
 - `python_ver`: the version of Python to use as a base for any built docker containers (default: 3.11)
 - `local`: a boolean flag indicating if invoke tasks should be run on the host or inside the docker containers (default: False, commands will be run in docker containers)
 - `compose_dir`: the full path to a directory containing the project compose files
@@ -57,8 +57,6 @@ To either stop or destroy the development environment use the following options.
 ---
 nautobot_plugin_nornir:
   local: true
-  compose_files:
-    - "docker-compose.requirements.yml"
 ```
 
 Run the following commands:
@@ -66,7 +64,7 @@ Run the following commands:
 ```shell
 poetry shell
 poetry install --extras nautobot
-export $(cat development/dev.env | xargs)
+export $(cat development/development.env | xargs)
 export $(cat development/creds.env | xargs)
 invoke start && sleep 5
 nautobot-server migrate
@@ -101,9 +99,6 @@ The project features a CLI helper based on [Invoke](https://www.pyinvoke.org/) t
 
 Each command can be executed with `invoke <command>`. All commands support the arguments `--nautobot-ver` and `--python-ver` if you want to manually define the version of Python and Nautobot to use. Each command also has its own help `invoke <command> --help`
 
-!!! note
-    To run the mysql (mariadb) development environment, set the environment variable as such `export NAUTOBOT_USE_MYSQL=1`.
-
 #### Local Development Environment
 
 ```
@@ -136,7 +131,6 @@ Each command can be executed with `invoke <command>`. All commands support the a
   unittest         Run Django unit tests for the plugin.
 ```
 
-
 ## Project Overview
 
 This project provides the ability to develop and manage the Nautobot server locally (with supporting services being *Dockerized*) or by using only Docker containers to manage Nautobot. The main difference between the two environments is the ability to debug and use **pdb** when developing locally. Debugging with **pdb** within the Docker container is more complicated, but can still be accomplished by either entering into the container (via `docker exec`) or attaching your IDE to the container and running the Nautobot service manually within the container.
@@ -185,7 +179,7 @@ The first thing you need to do is build the necessary Docker image for Nautobot
 #14 exporting layers
 #14 exporting layers 1.2s done
 #14 writing image sha256:2d524bc1665327faa0d34001b0a9d2ccf450612bf8feeb969312e96a2d3e3503 done
-#14 naming to docker.io/nautobot-plugin-nornir/nautobot:latest-py3.11 done
+#14 naming to docker.io/nautobot-plugin-nornir/nautobot:2.0.0-py3.11 done
 ```
 
 ### Invoke - Starting the Development Environment
@@ -216,9 +210,9 @@ This will start all of the Docker containers used for hosting Nautobot. You shou
 ```bash
 ➜ docker ps
 ****CONTAINER ID   IMAGE                            COMMAND                  CREATED          STATUS          PORTS                                       NAMES
-ee90fbfabd77   nautobot-plugin-nornir/nautobot:latest-py3.11   "nautobot-server rqw…"   16 seconds ago   Up 13 seconds                                               nautobot_plugin_nornir_worker_1
-b8adb781d013   nautobot-plugin-nornir/nautobot:latest-py3.11   "/docker-entrypoint.…"   20 seconds ago   Up 15 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   nautobot_plugin_nornir_nautobot_1
-d64ebd60675d   nautobot-plugin-nornir/nautobot:latest-py3.11   "mkdocs serve -v -a …"   25 seconds ago   Up 18 seconds   0.0.0.0:8001->8080/tcp, :::8001->8080/tcp   nautobot_plugin_nornir_docs_1
+ee90fbfabd77   nautobot-plugin-nornir/nautobot:2.0.0-py3.11  "nautobot-server rqw…"   16 seconds ago   Up 13 seconds                                               nautobot_plugin_nornir_worker_1
+b8adb781d013   nautobot-plugin-nornir/nautobot:2.0.0-py3.11  "/docker-entrypoint.…"   20 seconds ago   Up 15 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   nautobot_plugin_nornir_nautobot_1
+d64ebd60675d   nautobot-plugin-nornir/nautobot:2.0.0-py3.11  "mkdocs serve -v -a …"   25 seconds ago   Up 18 seconds   0.0.0.0:8001->8080/tcp, :::8001->8080/tcp   nautobot_plugin_nornir_docs_1
 e72d63129b36   postgres:13-alpine               "docker-entrypoint.s…"   25 seconds ago   Up 19 seconds   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   nautobot_plugin_nornir_postgres_1
 96c6ff66997c   redis:6-alpine                   "docker-entrypoint.s…"   25 seconds ago   Up 21 seconds   0.0.0.0:6379->6379/tcp, :::6379->6379/tcp   nautobot_plugin_nornir_redis_1
 ```
@@ -319,6 +313,9 @@ When trying to debug an issue, one helpful thing you can look at are the logs wi
 !!! note
 	The `-f` tag will keep the logs open, and output them in realtime as they are generated.
 
+!!! info
+    Want to limit the log output even further? Use the `--tail <#>` command line argument in conjunction with `-f`.
+
 So for example, our plugin is named `nautobot-plugin-nornir`, the command would most likely be `docker logs nautobot_plugin_nornir_nautobot_1 -f`. You can find the name of all running containers via `docker ps`.
 
 If you want to view the logs specific to the worker container, simply use the name of that container instead.
@@ -401,7 +398,7 @@ namespace.configure(
 )
 ```
 
-Or set the `INVOKE_NAUTOBOT_GOLDEN_CONFIG_PYTHON_VER` variable.
+Or set the `INVOKE_NAUTOBOT_PLUGIN_NORNIR_PYTHON_VER` variable.
 
 ### Updating Nautobot Version
 
@@ -413,7 +410,7 @@ namespace.configure(
     {
         "nautobot_plugin_nornir": {
             ...
-            "nautobot_ver": "1.0.2",
+            "nautobot_ver": "2.0.0",
 	    ...
         }
     }
diff --git a/docs/dev/dev_extending.md b/docs/dev/extending.md
similarity index 100%
rename from docs/dev/dev_extending.md
rename to docs/dev/extending.md
diff --git a/docs/requirements.txt b/docs/requirements.txt
index aa0cf44..3aed102 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,5 @@
 mkdocs==1.5.2
 mkdocs-material==9.2.4
+mkdocs-version-annotations==1.0.0
 mkdocstrings==0.22.0
-mkdocstrings-python==1.5.2
\ No newline at end of file
+mkdocstrings-python==1.5.2
diff --git a/docs/user/app_getting_started.md b/docs/user/app_getting_started.md
index dee7ae0..3e3b03e 100644
--- a/docs/user/app_getting_started.md
+++ b/docs/user/app_getting_started.md
@@ -4,12 +4,12 @@ A step-by-step tutorial on how to get the App going and how to use it.
 
 ## Install the App
 
-To install the App, please follow the instructions detailed in the [Installation Guide](../admin/admin_install.md).
+To install the App, please follow the instructions detailed in the [Installation Guide](../admin/install.md).
 
 ## Credentials
 
-The plugin allows you to integrate via a variety of methods to define your credentials, for details on what the options, see the [feature overview](../app_feature_credentials).
+The plugin allows you to integrate via a variety of methods to define your credentials, for details on what the options, see the [feature overview](./app_feature_credentials).
 
 ## Inventory
 
-The plugin allows you integrate Nornir with inventory, for details on what the options, see the [feature overview](../app_feature_inventory).
\ No newline at end of file
+The plugin allows you integrate Nornir with inventory, for details on what the options, see the [feature overview](./app_feature_inventory).
diff --git a/docs/user/app_overview.md b/docs/user/app_overview.md
index 1e380b4..95c5367 100644
--- a/docs/user/app_overview.md
+++ b/docs/user/app_overview.md
@@ -1,6 +1,11 @@
 # App Overview
 
-## Description/Overview
+This document provides an overview of the App including critical information and import considerations when applying it to your Nautobot environment.
+
+!!! note
+    Throughout this documentation, the terms "app" and "plugin" will be used interchangeably.
+
+## Description
 
 A plugin for [Nautobot](https://github.com/nautobot/nautobot), that intends to be a small shim layer between [nornir-nautobot](https://github.com/nautobot/nornir-nautobot) and other plugins. The primary abilities that the plugin provides is a native Nornir ORM based inventory and a credential manager.
 
@@ -20,3 +25,7 @@ That being said, there is currently little reason to install this plugin by itse
 
 * itdependsnetworks
 * jeffkala
+
+## Nautobot Features Used
+
+### Extras
diff --git a/docs/user/app_use_cases.md b/docs/user/app_use_cases.md
index b15897c..8c3af56 100644
--- a/docs/user/app_use_cases.md
+++ b/docs/user/app_use_cases.md
@@ -1,5 +1,7 @@
 # Using the App
 
+This document describes common use-cases and scenarios for this App.
+
 ## General Usage
 
 Leveraging Nornir with direct integrations with Nautobot for Inventory and Credentials.
@@ -10,4 +12,8 @@ On a high-level, all things Nornir
 
 * Using Jobs to create and integrate
 * Building Nornir that requires would like inventory and/or credentials
-* Using Nornir via an RestAPI and UI for integrations with other applications
\ No newline at end of file
+* Using Nornir via an RestAPI and UI for integrations with other applications
+
+## Screenshots
+
+![Backup Job](https://raw.githubusercontent.com/nautobot/nautobot-plugin-nornir/develop/docs/images/nornir-backup-job.png)
diff --git a/docs/user/app_external_interactions.md b/docs/user/external_interactions.md
similarity index 100%
rename from docs/user/app_external_interactions.md
rename to docs/user/external_interactions.md
diff --git a/docs/user/app_faq.md b/docs/user/faq.md
similarity index 100%
rename from docs/user/app_faq.md
rename to docs/user/faq.md
diff --git a/invoke.example.yml b/invoke.example.yml
index a5e9700..12e565c 100644
--- a/invoke.example.yml
+++ b/invoke.example.yml
@@ -1,7 +1,7 @@
 ---
 nautobot_plugin_nornir:
   project_name: "nautobot-plugin-nornir"
-  nautobot_ver: "latest"
+  nautobot_ver: "2.0.0"
   local: false
   python_ver: "3.11"
   compose_dir: "development"
diff --git a/invoke.mysql.yml b/invoke.mysql.yml
index 2e95dce..ce7eefd 100644
--- a/invoke.mysql.yml
+++ b/invoke.mysql.yml
@@ -1,7 +1,7 @@
 ---
 nautobot_plugin_nornir:
   project_name: "nautobot-plugin-nornir"
-  nautobot_ver: "latest"
+  nautobot_ver: "2.0.0"
   local: false
   python_ver: "3.11"
   compose_dir: "development"
diff --git a/mkdocs.yml b/mkdocs.yml
index 2a01dbb..f1f31e0 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -2,8 +2,8 @@
 dev_addr: "127.0.0.1:8001"
 edit_uri: "edit/main/nautobot-plugin-nornir/docs"
 site_dir: "nautobot_plugin_nornir/static/nautobot_plugin_nornir/docs"
-site_name: "Nautobot Plugin Nornir Documentation"
-site_url: "https://docs.nautobot.com/projects/nautobot-plugin-nornir/en/latest/"
+site_name: "Nautobot Nornir Plugin Documentation"
+site_url: "https://docs.nautobot.com/projects/plugin-nornir/en/latest/"
 repo_url: "https://github.com/nautobot/nautobot-plugin-nornir"
 copyright: "Copyright &copy; The Authors"
 theme:
@@ -14,13 +14,17 @@ theme:
     - "django"
     - "yaml"
   features:
-    - "navigation.tracking"
+    - "content.action.edit"
+    - "content.action.view"
+    - "content.code.copy"
+    - "navigation.footer"
+    - "navigation.indexes"
     - "navigation.tabs"
     - "navigation.tabs.sticky"
-    - "search.suggest"
+    - "navigation.tracking"
     - "search.highlight"
     - "search.share"
-    - "navigation.indexes"
+    - "search.suggest"
   favicon: "assets/favicon.ico"
   logo: "assets/nautobot_logo.svg"
   palette:
@@ -78,8 +82,10 @@ markdown_extensions:
   - "pymdownx.inlinehilite"
   - "pymdownx.snippets"
   - "pymdownx.superfences"
+  - "footnotes"
 plugins:
   - "search"
+  - "mkdocs-version-annotations"
   - "mkdocstrings":
       default_handler: "python"
       handlers:
@@ -94,16 +100,16 @@ nav:
   - Overview: "index.md"
   - User Guide:
       - App Overview: "user/app_overview.md"
-      - Using the App: "user/app_use_cases.md"
       - Getting Started: "user/app_getting_started.md"
-      - Navigate Credentials: "user/app_feature_credentials.md"
-      - Navigate Inventory: "user/app_feature_inventory.md"
-      - Frequently Asked Questions: "user/app_faq.md"
-      - External Interactions: "user/app_external_interactions.md"
+      - Using the App: "user/app_use_cases.md"
+      - Credentials: "user/app_feature_credentials.md"
+      - Inventory: "user/app_feature_inventory.md"
+      - Frequently Asked Questions: "user/faq.md"
+      - External Interactions: "user/external_interactions.md"
   - Administrator Guide:
-      - Install and Configure: "admin/admin_install.md"
-      - Upgrade: "admin/admin_upgrade.md"
-      - Uninstall: "admin/admin_uninstall.md"
+      - Install and Configure: "admin/install.md"
+      - Upgrade: "admin/upgrade.md"
+      - Uninstall: "admin/uninstall.md"
       - Compatibility Matrix: "admin/compatibility_matrix.md"
       - Troubleshooting:
           - "admin/troubleshooting/index.md"
@@ -114,14 +120,17 @@ nav:
       - Migrating To v2: "admin/migrating_to_v2.md"
       - Release Notes:
           - "admin/release_notes/index.md"
+          - v2.0: "admin/release_notes/version_2.0.md"
           - v1.0: "admin/release_notes/version_1.0.md"
           - v0.9: "admin/release_notes/version_0.9.md"
   - Developer Guide:
-      - Extending the App: "dev/dev_extending.md"
-      - Contributing to the App: "dev/dev_contributing.md"
+      - Extending the App: "dev/extending.md"
+      - Contributing to the App: "dev/contributing.md"
       - Development Environment: "dev/dev_environment.md"
+      - Architecture Decision Records: "dev/arch_decision.md"
       - Code Reference:
           - "dev/code_reference/index.md"
+          - Package: "dev/code_reference/package.md"
           - Nornir Credentials: "dev/code_reference/credentials.md"
           - Nornir Inventory: "dev/code_reference/inventory.md"
   - Nautobot Docs Home ↗︎: "https://docs.nautobot.com"
diff --git a/nautobot_plugin_nornir/__init__.py b/nautobot_plugin_nornir/__init__.py
index 12d02ca..e7db92e 100644
--- a/nautobot_plugin_nornir/__init__.py
+++ b/nautobot_plugin_nornir/__init__.py
@@ -1,15 +1,14 @@
 """Plugin declaration for nautobot_plugin_nornir."""
-
+# Metadata is inherited from Nautobot. If not including Nautobot in the environment, this should be added
 from importlib import metadata
 
-
 __version__ = metadata.version(__name__)
 
-from nautobot.extras.plugins import PluginConfig
+from nautobot.extras.plugins import NautobotAppConfig
 from nautobot_plugin_nornir.utils import verify_setting
 
 
-class NornirConfig(PluginConfig):
+class NautobotPluginNornirConfig(NautobotAppConfig):
     """Plugin configuration for nautobot_plugin_nornir."""
 
     name = "nautobot_plugin_nornir"
@@ -69,4 +68,4 @@ def ready(self):
         super().ready()
 
 
-config = NornirConfig  # pylint:disable=invalid-name
+config = NautobotPluginNornirConfig  # pylint:disable=invalid-name
diff --git a/nautobot_plugin_nornir/plugins/credentials/nautobot_secrets.py b/nautobot_plugin_nornir/plugins/credentials/nautobot_secrets.py
index ba33533..3eaf568 100644
--- a/nautobot_plugin_nornir/plugins/credentials/nautobot_secrets.py
+++ b/nautobot_plugin_nornir/plugins/credentials/nautobot_secrets.py
@@ -1,42 +1,118 @@
-"""Credentials class designed to work with Nautobot Secrets Functionality."""
+"""
+Credentials class designed to work with Nautobot Secrets Functionality.
+
+Nautobot Secrets Feature:
+
+secret-group:
+    secret: username <supports templating>
+        - Because of templating can be `n` number of actual values.
+    secret: password <supports templating>
+        - Because of templating can be `n` number of actual values.
+    secret: secret <supports templating>
+        - Because of templating can be `n` number of actual values.
+
+Caching Solution:
+
+creds_cache = {
+        "hashed key": "value"
+    }
+
+    - "hashed key" is the rendred.parameter which is post template rendered secret key.
+    - "value" is the literal secrets value.
+"""
 # pylint: disable=attribute-defined-outside-init
+import json
 
 from nautobot.extras.choices import SecretsGroupAccessTypeChoices, SecretsGroupSecretTypeChoices
-from nautobot.extras.models.secrets import SecretsGroupAssociation
 from nautobot_plugin_nornir.constants import PLUGIN_CFG
 
 from .nautobot_orm import MixinNautobotORMCredentials
 
 
-def _get_secret_value(secret_type, device_obj):
-    """Get value for a secret based on secret type and device.
+def _get_access_type_value(device_obj):
+    """Get value for access_type.
 
     Args:
-        secret_type (SecretsGroupSecretTypeChoices): Type of secret to check.
         device_obj (dcim.models.Device): Nautobot device object.
 
     Returns:
-        str: Secret value.
+        SecretsGroupAccessTypeChoices: Choice
     """
     if PLUGIN_CFG.get("use_config_context", {}).get("secrets"):
         access_type_str = device_obj.get_config_context()["nautobot_plugin_nornir"]["secret_access_type"].upper()
         access_type = getattr(SecretsGroupAccessTypeChoices, f"TYPE_{access_type_str}")
     else:
         access_type = SecretsGroupAccessTypeChoices.TYPE_GENERIC
-    try:
-        value = device_obj.secrets_group.get_secret_value(
-            access_type=access_type,
-            secret_type=secret_type,
-            obj=device_obj,
-        )
-    except SecretsGroupAssociation.DoesNotExist:
-        value = None
-    return value
+    return access_type
 
 
 class CredentialsNautobotSecrets(MixinNautobotORMCredentials):
     """Credentials Class designed to work with Nautobot Secrets Functionality."""
 
+    def __init__(self):
+        """Initialize class with empty creds_cache."""
+        self._creds_cache = {}
+
+    def _get_or_cache_secret_key(self, device, sec):
+        """Check if secret_key is already in cache, if not call setter method to add the entry.
+
+        Args:
+            device (dcim.Device): Nautobot Device object.
+            sec (extra.SecretGroup): Nautobot SecretGroup objects.
+
+        Returns:
+            str: A rendered secgret group hashed into a single hashed id to use as a unique key.
+
+        Examples:
+            >>> # Example of a Environment Variable rendered.
+            >>> device = Device.objects.first()
+            >>> sec = device.secrets_group.secrets.last()
+            >>> sec
+            >>> <Secret: router-u>
+            >>> sec.rendered_parameters(obj=device)
+            >>> {'variable': 'DEVICE_ROUTER_USERNAME'}
+            >>> str(hash(json.dumps(sec.rendered_parameters(obj=device), sort_keys=True)))
+            >>> '588946476233721127'
+            >>>
+            >>> # Example using hashicorp vault secrets provider backend.
+            >>> sec = device.secrets_group.secrets.first()
+            >>> sec.rendered_parameters(obj=device)
+            >>>
+            {'key': 'username',
+            'path': 'goldenconfig',
+            'kv_version': 'v2',
+            'mount_point': 'secret'}
+            >>> str(hash(json.dumps(sec.rendered_parameters(obj=device), sort_keys=True)))
+            >>> '-3888945057722956687'
+        """
+        # hash the rendered secrets params.
+        secret_key_hash = str(hash(json.dumps(sec.rendered_parameters(obj=device), sort_keys=True)))
+        if not self.creds_cache.get(secret_key_hash):
+            # If hashed value isn't in the cache, then call actual get_value to pull secret value itself and
+            # Update the cache property.
+            self.creds_cache = {secret_key_hash: sec.get_value(obj=device)}
+        return secret_key_hash
+
+    @property
+    def creds_cache(self):
+        """
+        Getter for in memory creds cache. This is useds to temporarily cache secrets-group creds to avoid re-querying secrets providers over and over per device if the same secret-group was used.
+
+        Example:
+            {"123435": 'supersecret'}
+        """
+        return self._creds_cache
+
+    @creds_cache.setter
+    def creds_cache(self, new_cred):
+        """
+        Setter for creds_cache.
+
+        Args:
+            new_cred (dict): new secret group key and values.
+        """
+        self._creds_cache.update(new_cred)
+
     def get_device_creds(self, device):
         """Return the credentials for a given device.
 
@@ -49,13 +125,31 @@ def get_device_creds(self, device):
             secret (string):
         """
         if device.secrets_group:
-            self.username = _get_secret_value(
-                secret_type=SecretsGroupSecretTypeChoices.TYPE_USERNAME, device_obj=device
-            )
-            self.password = _get_secret_value(
-                secret_type=SecretsGroupSecretTypeChoices.TYPE_PASSWORD, device_obj=device
-            )
-            self.secret = _get_secret_value(secret_type=SecretsGroupSecretTypeChoices.TYPE_SECRET, device_obj=device)
+            self.secret = None
+            for sec in device.secrets_group.secrets.all():
+                secret_value = self.creds_cache.get(self._get_or_cache_secret_key(device, sec))
+                current_secret_type = getattr(
+                    SecretsGroupSecretTypeChoices, f"TYPE_{sec.secrets_group_associations.first().secret_type.upper()}"
+                )
+                current_access_type = getattr(
+                    SecretsGroupAccessTypeChoices, f"TYPE_{sec.secrets_group_associations.first().access_type.upper()}"
+                )
+                configured_access_type = _get_access_type_value(device)
+                if (
+                    current_secret_type == SecretsGroupSecretTypeChoices.TYPE_USERNAME
+                    and configured_access_type == current_access_type
+                ):
+                    self.username = secret_value
+                if (
+                    current_secret_type == SecretsGroupSecretTypeChoices.TYPE_PASSWORD
+                    and configured_access_type == current_access_type
+                ):
+                    self.password = secret_value
+                if (
+                    current_secret_type == SecretsGroupSecretTypeChoices.TYPE_SECRET
+                    and configured_access_type == current_access_type
+                ):
+                    self.secret = secret_value
             if not self.secret:
                 self.secret = self.password
             return (self.username, self.password, self.secret)
diff --git a/poetry.lock b/poetry.lock
index 963ce74..78378c6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1520,17 +1520,6 @@ files = [
     {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"},
 ]
 
-[[package]]
-name = "iniconfig"
-version = "2.0.0"
-description = "brain-dead simple config-ini parsing"
-optional = false
-python-versions = ">=3.7"
-files = [
-    {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
-    {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
-]
-
 [[package]]
 name = "invoke"
 version = "2.2.0"
@@ -2139,6 +2128,17 @@ files = [
     {file = "mkdocs_material_extensions-1.2.tar.gz", hash = "sha256:27e2d1ed2d031426a6e10d5ea06989d67e90bb02acd588bc5673106b5ee5eedf"},
 ]
 
+[[package]]
+name = "mkdocs-version-annotations"
+version = "1.0.0"
+description = "MkDocs plugin to add custom admonitions for documenting version differences"
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+    {file = "mkdocs-version-annotations-1.0.0.tar.gz", hash = "sha256:6786024b37d27b330fda240b76ebec8e7ce48bd5a9d7a66e99804559d088dffa"},
+    {file = "mkdocs_version_annotations-1.0.0-py3-none-any.whl", hash = "sha256:385004eb4a7530dd87a227e08cd907ce7a8fe21fdf297720a4149c511bcf05f5"},
+]
+
 [[package]]
 name = "mkdocstrings"
 version = "0.22.0"
@@ -2675,21 +2675,6 @@ files = [
 docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
 test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
 
-[[package]]
-name = "pluggy"
-version = "1.3.0"
-description = "plugin and hook calling mechanisms for python"
-optional = false
-python-versions = ">=3.8"
-files = [
-    {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
-    {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
-]
-
-[package.extras]
-dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
-
 [[package]]
 name = "prometheus-client"
 version = "0.17.1"
@@ -3110,28 +3095,6 @@ files = [
 [package.extras]
 cp2110 = ["hidapi"]
 
-[[package]]
-name = "pytest"
-version = "7.4.2"
-description = "pytest: simple powerful testing with Python"
-optional = false
-python-versions = ">=3.7"
-files = [
-    {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"},
-    {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"},
-]
-
-[package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
-pluggy = ">=0.12,<2.0"
-tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
-
-[package.extras]
-testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
-
 [[package]]
 name = "python-crontab"
 version = "3.0.0"
@@ -4299,10 +4262,7 @@ files = [
 docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
 testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
 
-[extras]
-nautobot = ["nautobot"]
-
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.8,<3.12"
-content-hash = "0ab4ccdd5b969fac34a5d622c528f496c0a97cc703ef5cfaf86868b4850d97ba"
+content-hash = "07b9232cdc1b84ec7c7cd7d22a70b73b451e55b72066ea73cf99315d934e8e80"
diff --git a/pyproject.toml b/pyproject.toml
index 1d7aef3..f52f7f5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "nautobot-plugin-nornir"
-version = "2.0.0"
+version = "2.0.1"
 description = "Nautobot Nornir plugin."
 authors = ["Network to Code, LLC <info@networktocode.com>"]
 license = "Apache-2.0"
@@ -8,10 +8,6 @@ readme = "README.md"
 homepage = "https://github.com/nautobot/nautobot-plugin-nornir"
 repository = "https://github.com/nautobot/nautobot-plugin-nornir"
 keywords = ["nautobot", "nautobot-plugin"]
-include = [
-    "LICENSE",
-    "README.md",
-]
 classifiers = [
     "Intended Audience :: Developers",
     "Development Status :: 5 - Production/Stable",
@@ -21,6 +17,10 @@ classifiers = [
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
 ]
+include = [
+    "LICENSE",
+    "README.md",
+]
 packages = [
     { include = "nautobot_plugin_nornir" },
 ]
@@ -31,6 +31,7 @@ packages = [
 
 [tool.poetry.dependencies]
 python = ">=3.8,<3.12"
+# Used for local development
 nautobot = "^2.0.0"
 nornir-nautobot = "^3.0.0"
 netutils = ">=1.6.0"
@@ -40,33 +41,29 @@ bandit = "*"
 black = "*"
 coverage = "*"
 django-debug-toolbar = "*"
-# we need to pin flake8 because of package dependencies that cause it to downgrade and
-# therefore cause issues with linting since older versions do not take .flake8 as config
-flake8 = "^3.9.2"
+flake8 = "*"
 invoke = "*"
 ipython = "*"
 pydocstyle = "*"
 pylint = "*"
 pylint-django = "*"
 pylint-nautobot = "*"
-pytest = "*"
 yamllint = "*"
-Markdown = "*"
 toml = "*"
+Markdown = "*"
 # Rendering docs to HTML
 mkdocs = "1.5.2"
 # Material for MkDocs theme
 mkdocs-material = "9.2.4"
+# Render custom markdown for version added/changed/remove notes
+mkdocs-version-annotations = "1.0.0"
 # Automatic documentation from sources, for MkDocs
 mkdocstrings = "0.22.0"
 mkdocstrings-python = "1.5.2"
 
-[tool.poetry.extras]
-nautobot = ["nautobot"]
-
 [tool.black]
 line-length = 120
-target-version = ['py37']
+target-version = ['py38', 'py39', 'py310', 'py311']
 include = '\.pyi?$'
 exclude = '''
 (
@@ -89,7 +86,7 @@ exclude = '''
 
 [tool.pylint.master]
 # Include the pylint_django plugin to avoid spurious warnings about Django patterns
-load-plugins="pylint_django,pylint_nautobot"
+load-plugins="pylint_django, pylint_nautobot"
 ignore=".venv"
 
 [tool.pylint.basic]
@@ -110,6 +107,11 @@ notes = """,
     XXX,
     """
 
+[tool.pylint-nautobot]
+supported_nautobot_versions = [
+    "2.0.0"
+]
+
 [tool.pydocstyle]
 convention = "google"
 inherit = false
@@ -125,11 +127,3 @@ add_ignore = "D212"
 [build-system]
 requires = ["poetry_core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
-
-[tool.pytest.ini_options]
-testpaths = [
-    "tests"
-]
-addopts = "-vv --doctest-modules"
-[tool.pylint-nautobot]
-supported_nautobot_versions = ["2"]
\ No newline at end of file
diff --git a/tasks.py b/tasks.py
index 68a880d..d8af7a0 100644
--- a/tasks.py
+++ b/tasks.py
@@ -1,6 +1,6 @@
 """Tasks for use with Invoke.
 
-(c) 2020-2023 Network To Code
+Copyright (c) 2023, Network to Code, LLC
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
@@ -13,10 +13,9 @@
 """
 
 import os
-from distutils.util import strtobool
 
-from invoke import Collection
-from invoke import task as invoke_task
+from invoke.collection import Collection
+from invoke.tasks import task as invoke_task
 
 
 def is_truthy(arg):
@@ -31,7 +30,14 @@ def is_truthy(arg):
     """
     if isinstance(arg, bool):
         return arg
-    return bool(strtobool(arg))
+
+    val = str(arg).lower()
+    if val in ("y", "yes", "t", "true", "on", "1"):
+        return True
+    elif val in ("n", "no", "f", "false", "off", "0"):
+        return False
+    else:
+        raise ValueError(f"Invalid truthy value: `{arg}`")
 
 
 # Use pyinvoke configuration for default values, see http://docs.pyinvoke.org/en/stable/concepts/configuration.html
@@ -41,7 +47,7 @@ def is_truthy(arg):
     {
         "nautobot_plugin_nornir": {
             "nautobot_ver": "2.0.0",
-            "project_name": "nautobot_plugin_nornir",
+            "project_name": "nautobot-plugin-nornir",
             "python_ver": "3.11",
             "local": False,
             "compose_dir": os.path.join(os.path.dirname(__file__), "development"),
@@ -57,6 +63,10 @@ def is_truthy(arg):
 )
 
 
+def _is_compose_included(context, name):
+    return f"docker-compose.{name}.yml" in context.nautobot_plugin_nornir.compose_files
+
+
 def task(function=None, *args, **kwargs):
     """Task decorator to override the default Invoke task decorator and add each task to the invoke namespace."""
 
@@ -77,22 +87,23 @@ def task_wrapper(function=None):
 
 
 def docker_compose(context, command, **kwargs):
-    """Helper function for running a specific docker-compose command with all appropriate parameters and environment.
+    """Helper function for running a specific docker compose command with all appropriate parameters and environment.
 
     Args:
         context (obj): Used to run specific commands
-        command (str): Command string to append to the "docker-compose ..." command, such as "build", "up", etc.
+        command (str): Command string to append to the "docker compose ..." command, such as "build", "up", etc.
         **kwargs: Passed through to the context.run() call.
     """
     build_env = {
-        # Note: 'docker-compose logs' will stop following after 60 seconds by default,
+        # Note: 'docker compose logs' will stop following after 60 seconds by default,
         # so we are overriding that by setting this environment variable.
         "COMPOSE_HTTP_TIMEOUT": context.nautobot_plugin_nornir.compose_http_timeout,
         "NAUTOBOT_VER": context.nautobot_plugin_nornir.nautobot_ver,
         "PYTHON_VER": context.nautobot_plugin_nornir.python_ver,
+        **kwargs.pop("env", {}),
     }
     compose_command_tokens = [
-        "docker-compose",
+        "docker compose",
         f"--project-name {context.nautobot_plugin_nornir.project_name}",
         f'--project-directory "{context.nautobot_plugin_nornir.compose_dir}"',
     ]
@@ -108,7 +119,7 @@ def docker_compose(context, command, **kwargs):
     if service is not None:
         compose_command_tokens.append(service)
 
-    print(f'Running docker-compose command "{command}"')
+    print(f'Running docker compose command "{command}"')
     compose_command = " ".join(compose_command_tokens)
 
     return context.run(compose_command, env=build_env, **kwargs)
@@ -125,9 +136,11 @@ def run_command(context, command, **kwargs):
         if "nautobot" in results.stdout:
             compose_command = f"exec nautobot {command}"
         else:
-            compose_command = f"run --entrypoint '{command}' nautobot"
+            compose_command = f"run --rm --entrypoint '{command}' nautobot"
+
+        pty = kwargs.pop("pty", True)
 
-        docker_compose(context, compose_command, pty=True)
+        docker_compose(context, compose_command, pty=pty, **kwargs)
 
 
 # ------------------------------------------------------------------------------
@@ -159,42 +172,73 @@ def generate_packages(context):
     run_command(context, command)
 
 
+@task(
+    help={
+        "check": (
+            "If enabled, check for outdated dependencies in the poetry.lock file, "
+            "instead of generating a new one. (default: disabled)"
+        )
+    }
+)
+def lock(context, check=False):
+    """Generate poetry.lock inside the Nautobot container."""
+    run_command(context, f"poetry {'check' if check else 'lock --no-update'}")
+
+
 # ------------------------------------------------------------------------------
 # START / STOP / DEBUG
 # ------------------------------------------------------------------------------
-@task
-def debug(context):
-    """Start Nautobot and its dependencies in debug mode."""
-    print("Starting Nautobot in debug mode...")
-    docker_compose(context, "up")
+@task(help={"service": "If specified, only affect this service."})
+def debug(context, service=""):
+    """Start specified or all services and its dependencies in debug mode."""
+    print(f"Starting {service} in debug mode...")
+    docker_compose(context, "up", service=service)
 
 
 @task(help={"service": "If specified, only affect this service."})
-def start(context, service=None):
-    """Start Nautobot and its dependencies in detached mode."""
+def start(context, service=""):
+    """Start specified or all services and its dependencies in detached mode."""
     print("Starting Nautobot in detached mode...")
     docker_compose(context, "up --detach", service=service)
 
 
-@task
-def restart(context):
-    """Gracefully restart all containers."""
+@task(help={"service": "If specified, only affect this service."})
+def restart(context, service=""):
+    """Gracefully restart specified or all services."""
     print("Restarting Nautobot...")
-    docker_compose(context, "restart")
+    docker_compose(context, "restart", service=service)
 
 
-@task
-def stop(context):
-    """Stop Nautobot and its dependencies."""
+@task(help={"service": "If specified, only affect this service."})
+def stop(context, service=""):
+    """Stop specified or all services, if service is not specified, remove all containers."""
     print("Stopping Nautobot...")
-    docker_compose(context, "down")
+    docker_compose(context, "stop" if service else "down --remove-orphans", service=service)
 
 
 @task
 def destroy(context):
     """Destroy all containers and volumes."""
     print("Destroying Nautobot...")
-    docker_compose(context, "down --volumes")
+    docker_compose(context, "down --remove-orphans --volumes")
+
+
+@task
+def export(context):
+    """Export docker compose configuration to `compose.yaml` file.
+
+    Useful to:
+
+    - Debug docker compose configuration.
+    - Allow using `docker compose` command directly without invoke.
+    """
+    docker_compose(context, "convert > compose.yaml")
+
+
+@task(name="ps", help={"all": "Show all, including stopped containers"})
+def ps_task(context, all=False):
+    """List containers."""
+    docker_compose(context, f"ps {'--all' if all else ''}")
 
 
 @task
@@ -207,13 +251,13 @@ def vscode(context):
 
 @task(
     help={
-        "service": "Docker-compose service name to view (default: nautobot)",
-        "follow": "Follow logs",
-        "tail": "Tail N number of lines or 'all'",
+        "service": "If specified, only display logs for this service (default: all)",
+        "follow": "Flag to follow logs (default: False)",
+        "tail": "Tail N number of lines (default: all)",
     }
 )
-def logs(context, service="nautobot", follow=False, tail=None):
-    """View the logs of a docker-compose service."""
+def logs(context, service="", follow=False, tail=0):
+    """View the logs of a docker compose service."""
     command = "logs "
 
     if follow:
@@ -221,18 +265,21 @@ def logs(context, service="nautobot", follow=False, tail=None):
     if tail:
         command += f"--tail={tail} "
 
-    command += service
-    docker_compose(context, command)
+    docker_compose(context, command, service=service)
 
 
 # ------------------------------------------------------------------------------
 # ACTIONS
 # ------------------------------------------------------------------------------
-@task
-def nbshell(context):
+@task(help={"file": "Python file to execute"})
+def nbshell(context, file=""):
     """Launch an interactive nbshell session."""
-    command = "nautobot-server nbshell"
-    run_command(context, command)
+    command = [
+        "nautobot-server",
+        "nbshell",
+        f"< '{file}'" if file else "",
+    ]
+    run_command(context, " ".join(command), pty=not bool(file))
 
 
 @task
@@ -244,7 +291,7 @@ def shell_plus(context):
 
 @task
 def cli(context):
-    """Launch a bash shell inside the running Nautobot container."""
+    """Launch a bash shell inside the Nautobot container."""
     run_command(context, "bash")
 
 
@@ -302,6 +349,165 @@ def post_upgrade(context):
     run_command(context, command)
 
 
+@task(
+    help={
+        "service": "Docker compose service name to run command in (default: nautobot).",
+        "command": "Command to run (default: bash).",
+        "file": "File to run command with (default: empty)",
+    },
+)
+def exec(context, service="nautobot", command="bash", file=""):
+    """Launch a command inside the running container (defaults to bash shell inside nautobot container)."""
+    command = [
+        "exec",
+        "--",
+        service,
+        command,
+        f"< '{file}'" if file else "",
+    ]
+    docker_compose(context, " ".join(command), pty=not bool(file))
+
+
+@task(
+    help={
+        "db-name": "Database name (default: Nautobot database)",
+        "input-file": "SQL file to execute and quit (default: empty, start interactive CLI)",
+        "output-file": "Ouput file, overwrite if exists (default: empty, output to stdout)",
+        "query": "SQL command to execute and quit (default: empty)",
+    }
+)
+def dbshell(context, db_name="", input_file="", output_file="", query=""):
+    """Start database CLI inside the running `db` container.
+
+    Doesn't use `nautobot-server dbshell`, using started `db` service container only.
+    """
+    if input_file and query:
+        raise ValueError("Cannot specify both, `input_file` and `query` arguments")
+    if output_file and not (input_file or query):
+        raise ValueError("`output_file` argument requires `input_file` or `query` argument")
+
+    env = {}
+    if query:
+        env["_SQL_QUERY"] = query
+
+    command = [
+        "exec",
+        "--env=_SQL_QUERY" if query else "",
+        "-- db sh -c '",
+    ]
+
+    if _is_compose_included(context, "mysql"):
+        command += [
+            "mysql",
+            "--user=$MYSQL_USER",
+            "--password=$MYSQL_PASSWORD",
+            f"--database={db_name or '$MYSQL_DATABASE'}",
+        ]
+    elif _is_compose_included(context, "postgres"):
+        command += [
+            "psql",
+            "--username=$POSTGRES_USER",
+            f"--dbname={db_name or '$POSTGRES_DB'}",
+        ]
+    else:
+        raise ValueError("Unsupported database backend.")
+
+    command += [
+        "'",
+        '<<<"$_SQL_QUERY"' if query else "",
+        f"< '{input_file}'" if input_file else "",
+        f"> '{output_file}'" if output_file else "",
+    ]
+
+    docker_compose(context, " ".join(command), env=env, pty=not (input_file or output_file or query))
+
+
+@task(
+    help={
+        "input-file": "SQL dump file to replace the existing database with. This can be generated using `invoke backup-db` (default: `dump.sql`).",
+    }
+)
+def import_db(context, input_file="dump.sql"):
+    """Stop Nautobot containers and replace the current database with the dump into the running `db` container."""
+    docker_compose(context, "stop -- nautobot worker")
+
+    command = ["exec -- db sh -c '"]
+
+    if _is_compose_included(context, "mysql"):
+        command += [
+            "mysql",
+            "--database=$MYSQL_DATABASE",
+            "--user=$MYSQL_USER",
+            "--password=$MYSQL_PASSWORD",
+        ]
+    elif _is_compose_included(context, "postgres"):
+        command += [
+            "psql",
+            "--username=$POSTGRES_USER",
+            "postgres",
+        ]
+    else:
+        raise ValueError("Unsupported database backend.")
+
+    command += [
+        "'",
+        f"< '{input_file}'",
+    ]
+
+    docker_compose(context, " ".join(command), pty=False)
+
+    print("Database import complete, you can start Nautobot now: `invoke start`")
+
+
+@task(
+    help={
+        "db-name": "Database name to backup (default: Nautobot database)",
+        "output-file": "Ouput file, overwrite if exists (default: `dump.sql`)",
+        "readable": "Flag to dump database data in more readable format (default: `True`)",
+    }
+)
+def backup_db(context, db_name="", output_file="dump.sql", readable=True):
+    """Dump database into `output_file` file from running `db` container."""
+    command = ["exec -- db sh -c '"]
+
+    if _is_compose_included(context, "mysql"):
+        command += [
+            "mysqldump",
+            "--user=root",
+            "--password=$MYSQL_ROOT_PASSWORD",
+            "--add-drop-database",
+            "--skip-extended-insert" if readable else "",
+            "--databases",
+            db_name if db_name else "$MYSQL_DATABASE",
+        ]
+    elif _is_compose_included(context, "postgres"):
+        command += [
+            "pg_dump",
+            "--clean",
+            "--create",
+            "--if-exists",
+            "--username=$POSTGRES_USER",
+            f"--dbname={db_name or '$POSTGRES_DB'}",
+            "--inserts" if readable else "",
+        ]
+    else:
+        raise ValueError("Unsupported database backend.")
+
+    command += [
+        "'",
+        f"> '{output_file}'",
+    ]
+
+    docker_compose(context, " ".join(command), pty=False)
+
+    print(50 * "=")
+    print("The database backup has been successfully completed and saved to the following file:")
+    print(output_file)
+    print("You can import this database backup with the following command:")
+    print(f"invoke import-db --input-file '{output_file}'")
+    print(50 * "=")
+
+
 # ------------------------------------------------------------------------------
 # DOCS
 # ------------------------------------------------------------------------------
@@ -317,6 +523,25 @@ def docs(context):
         start(context, service="docs")
 
 
+@task
+def build_and_check_docs(context):
+    """Build documentation to be available within Nautobot."""
+    command = "mkdocs build --no-directory-urls --strict"
+    run_command(context, command)
+
+
+@task(name="help")
+def help_task(context):
+    """Print the help of available tasks."""
+    import tasks  # pylint: disable=all
+
+    root = Collection.from_module(tasks)
+    for task_name in sorted(root.task_names):
+        print(50 * "-")
+        print(f"invoke {task_name} --help")
+        context.run(f"invoke {task_name} --help")
+
+
 # ------------------------------------------------------------------------------
 # TESTS
 # ------------------------------------------------------------------------------
@@ -375,7 +600,7 @@ def bandit(context):
 
 @task
 def yamllint(context):
-    """Run yamllint to validate formatting adheres to NTC defined YAML standards.
+    """Run yamllint to validate formating adheres to NTC defined YAML standards.
 
     Args:
         context (obj): Used to run specific commands
@@ -387,7 +612,7 @@ def yamllint(context):
 @task
 def check_migrations(context):
     """Check for missing migrations."""
-    command = "nautobot-server --config=nautobot/core/tests/nautobot_config.py makemigrations --dry-run --check"
+    command = "nautobot-server makemigrations --dry-run --check"
 
     run_command(context, command)
 
@@ -398,9 +623,19 @@ def check_migrations(context):
         "label": "specify a directory or module to test instead of running all Nautobot tests",
         "failfast": "fail as soon as a single test fails don't run the entire test suite",
         "buffer": "Discard output from passing tests",
+        "pattern": "Run specific test methods, classes, or modules instead of all tests",
+        "verbose": "Enable verbose test output.",
     }
 )
-def unittest(context, keepdb=False, label="nautobot_plugin_nornir", failfast=False, buffer=True):
+def unittest(
+    context,
+    keepdb=False,
+    label="nautobot_plugin_nornir",
+    failfast=False,
+    buffer=True,
+    pattern="",
+    verbose=False,
+):
     """Run Nautobot unit tests."""
     command = f"coverage run --module nautobot.core.cli test {label}"
 
@@ -410,6 +645,11 @@ def unittest(context, keepdb=False, label="nautobot_plugin_nornir", failfast=Fal
         command += " --failfast"
     if buffer:
         command += " --buffer"
+    if pattern:
+        command += f" -k='{pattern}'"
+    if verbose:
+        command += " --verbosity 2"
+
     run_command(context, command)
 
 
@@ -423,10 +663,12 @@ def unittest_coverage(context):
 
 @task(
     help={
-        "failfast": "fail as soon as a single test fails don't run the entire test suite",
+        "failfast": "fail as soon as a single test fails don't run the entire test suite. (default: False)",
+        "keepdb": "Save and re-use test database between test runs for faster re-testing. (default: False)",
+        "lint-only": "Only run linters; unit tests will be excluded. (default: False)",
     }
 )
-def tests(context, failfast=False):
+def tests(context, failfast=False, keepdb=False, lint_only=False):
     """Run all tests for this plugin."""
     # If we are not running locally, start the docker containers so we don't have to for each test
     if not is_truthy(context.nautobot_plugin_nornir.local):
@@ -443,9 +685,16 @@ def tests(context, failfast=False):
     pydocstyle(context)
     print("Running yamllint...")
     yamllint(context)
+    print("Running poetry check...")
+    lock(context, check=True)
+    print("Running migrations check...")
+    check_migrations(context)
     print("Running pylint...")
     pylint(context)
-    print("Running unit tests...")
-    unittest(context, failfast=failfast)
+    print("Running mkdocs...")
+    build_and_check_docs(context)
+    if not lint_only:
+        print("Running unit tests...")
+        unittest(context, failfast=failfast, keepdb=keepdb)
+        unittest_coverage(context)
     print("All tests have passed!")
-    unittest_coverage(context)


Test patch:
diff --git a/.github/workflows/upstream_testing.yml b/.github/workflows/upstream_testing.yml
index c1b6c51..e14212c 100644
--- a/.github/workflows/upstream_testing.yml
+++ b/.github/workflows/upstream_testing.yml
@@ -9,5 +9,5 @@ jobs:
   upstream-test:
     uses: "nautobot/nautobot/.github/workflows/plugin_upstream_testing_base.yml@develop"
     with:  # Below could potentially be collapsed into a single argument if a concrete relationship between both is enforced
-      invoke_context_name: "NORNIR"
+      invoke_context_name: "NAUTOBOT_PLUGIN_NORNIR"
       plugin_name: "nautobot-plugin-nornir"
diff --git a/nautobot_plugin_nornir/tests/__init__.py b/nautobot_plugin_nornir/tests/__init__.py
index f9f39e9..9e73939 100644
--- a/nautobot_plugin_nornir/tests/__init__.py
+++ b/nautobot_plugin_nornir/tests/__init__.py
@@ -1,1 +1,1 @@
-"""Unit tests for nautobot_plugin_nornir."""
+"""Unit tests for nautobot_plugin_nornir plugin."""