Back to Learn

Nornir — Python automation framework

A pure-Python alternative to Ansible: structured inventory, parallel task execution and reusable tasks — with no custom DSL. Plug in Netmiko or NAPALM as connection drivers and keep everything testable Python.

Python
Inventory
Parallel
Netmiko driver
NAPALM driver
No DSL
YAML inventory of hosts, groups and defaults
nr.run(task=...) executes tasks in parallel
Plugins add drivers (Netmiko, NAPALM) and tasks

Setup, key commands & run examples

Step 0 — install
Setup

Install Nornir plus the common plugins for inventory parsing, Netmiko tasks and structured output.

Step 0 — install
bash
python3 -m venv .venv
source .venv/bin/activate
pip install nornir nornir-netmiko nornir-utils
config.yaml — runner & inventory
Configuration

The main config points at your inventory files and sets the parallel runner worker count.

config.yaml — runner & inventory
yaml
inventory:
  plugin: SimpleInventory
  options:
    host_file: "hosts.yaml"
    group_file: "groups.yaml"
runner:
  plugin: threaded
  options:
    num_workers: 20
hosts.yaml — structured inventory
Inventory

Each host gets a platform and connection options. Groups (referenced here) hold shared credentials and data.

hosts.yaml — structured inventory
yaml
r1:
  hostname: 10.10.1.1
  platform: ios
  groups: [core]
r2:
  hostname: 10.10.1.2
  platform: ios
  groups: [core]
run.py — parallel task
nr.run

InitNornir loads config + inventory. nr.run executes the task across all hosts concurrently and aggregates results.

run.py — parallel task
python
from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_command
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")

result = nr.run(
    task=netmiko_send_command,
    command_string="show ip interface brief",
)
print_result(result)
filter.py — target a subset
Filtering

Use the F filter to scope a run to a group, site or attribute before executing — safer than touching everything.

filter.py — target a subset
python
from nornir.core.filter import F

core = nr.filter(F(groups__contains="core"))
core.run(task=netmiko_send_command, command_string="show version")

Best practices

  • Keep secrets out of inventory

    Load credentials from environment variables or a vault plugin, not hard-coded in hosts.yaml.

  • Filter before you run

    Scope runs with nr.filter(F(...)) so a mistake hits one group, not the whole fleet.

  • Tune num_workers deliberately

    More workers is faster but can overwhelm TACACS/AAA — start modest and raise it as you measure.

  • Inspect result.failed

    Iterate AggregatedResult and check per-host failures instead of assuming success across all devices.

Hands-on exercises

Guided practice tasks — copy the starter snippet, run it against a lab device, and extend it to meet the goal.

Exercise 1
Collect versions fleet-wide

Goal: Init Nornir, run netmiko_send_command for 'show version' across all hosts, and print_result.

exercise_1
python
from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_command
from nornir_utils.plugins.functions import print_result

nr = InitNornir(config_file="config.yaml")
# TODO: result = nr.run(task=netmiko_send_command, command_string="show version")
# TODO: print_result(result)
Exercise 2
Scope a run with a filter

Goal: Filter the inventory to the 'core' group and run a command only against those devices.

exercise_2
python
from nornir.core.filter import F

core = nr.filter(F(groups__contains="core"))
# TODO: core.run(task=netmiko_send_command, command_string="show ip int brief")
Exercise 3
Write your own task

Goal: Create a custom task function that returns a Result, then run it across the inventory.

exercise_3
python
from nornir.core.task import Task, Result

def hello(task: Task) -> Result:
    # TODO: return Result(host=task.host, result=f"hi from {task.host.name}")
    ...

nr.run(task=hello)

Curated videos & guides

Checklist — pitfalls, gotchas & verification

Common pitfalls

  • Hard-coding credentials in hosts.yaml — load them from env/vault instead.
  • Running against the whole inventory by accident — always filter first.
  • Cranking num_workers too high and overwhelming AAA/TACACS servers.

Gotchas to watch

  • InitNornir needs the config_file path relative to your working directory.
  • A failed host does not raise — you must inspect result.failed / failed_hosts.
  • Driver plugins are separate packages (nornir-netmiko, nornir-napalm) you must install.

Verify it works

  • Run print_result() and confirm each host shows output, not a traceback.
  • Check nr.data.failed_hosts is empty after the run.
  • Start with a 1-host filter to validate logic before scaling out.

Nornir gives you Ansible-style inventory and concurrency while staying in plain Python — ideal when your logic outgrows YAML playbooks.