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.
Setup, key commands & run examples
Install Nornir plus the common plugins for inventory parsing, Netmiko tasks and structured output.
python3 -m venv .venv
source .venv/bin/activate
pip install nornir nornir-netmiko nornir-utilsThe main config points at your inventory files and sets the parallel runner worker count.
inventory:
plugin: SimpleInventory
options:
host_file: "hosts.yaml"
group_file: "groups.yaml"
runner:
plugin: threaded
options:
num_workers: 20Each host gets a platform and connection options. Groups (referenced here) hold shared credentials and data.
r1:
hostname: 10.10.1.1
platform: ios
groups: [core]
r2:
hostname: 10.10.1.2
platform: ios
groups: [core]InitNornir loads config + inventory. nr.run executes the task across all hosts concurrently and aggregates results.
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)Use the F filter to scope a run to a group, site or attribute before executing — safer than touching everything.
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.
Goal: Init Nornir, run netmiko_send_command for 'show version' across all hosts, and print_result.
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)Goal: Filter the inventory to the 'core' group and run a command only against those devices.
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")Goal: Create a custom task function that returns a Result, then run it across the inventory.
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.