NAPALM โ vendor-neutral network automation
One consistent Python API across Cisco IOS/NX-OS, Arista EOS and Juniper JunOS. Pull structured facts with getters, and merge or replace config with safe diffs, atomic commit and rollback.
Setup, key commands & run examples
Install NAPALM into a virtualenv. It bundles the core drivers for EOS, JunOS, IOS, IOS-XR and NX-OS.
python3 -m venv .venv
source .venv/bin/activate
pip install napalmOpen a driver for the platform and call getters. Every getter returns normalised, vendor-independent dictionaries.
import napalm
driver = napalm.get_network_driver("ios")
device = driver(
hostname="10.10.1.1",
username="admin",
password="secret",
)
device.open()
print(device.get_facts())
print(device.get_interfaces_ip())
device.close()load_merge_candidate stages config without applying it. compare_config returns the diff so you can review before committing.
with driver("10.10.1.1", "admin", "secret") as device:
device.load_merge_candidate(filename="loopback.cfg")
print(device.compare_config())
# discard if it doesn't look right
device.discard_config()commit_config applies the staged candidate atomically. If something breaks, rollback() restores the previous running config.
with driver("10.10.1.1", "admin", "secret") as device:
device.load_replace_candidate(filename="full_config.cfg")
if device.compare_config():
device.commit_config()
# later, if needed:
# device.rollback()Best practices
Always diff before commit
Run compare_config() and review the output (or gate it in CI) so no change is applied blind.
Prefer merge over replace early on
load_merge_candidate is additive and safer; move to load_replace_candidate once you trust your full config templates.
Use context managers
with driver(...) guarantees sessions close even on error, avoiding stale locked candidate configs.
Normalise, don't screen-scrape
Lean on getters (get_facts, get_bgp_neighbors) for stable structured data instead of parsing raw CLI yourself.
Hands-on exercises
Guided practice tasks โ copy the starter snippet, run it against a lab device, and extend it to meet the goal.
Goal: Loop over three hosts, call get_facts() on each, and print hostname, model and OS version in a table.
import napalm
driver = napalm.get_network_driver("ios")
hosts = ["10.10.1.1", "10.10.1.2", "10.10.1.3"]
for host in hosts:
with driver(host, "admin", "secret") as dev:
f = dev.get_facts()
# TODO: print f["hostname"], f["model"], f["os_version"]
...Goal: Stage a loopback config, print compare_config(), and only commit when the diff is non-empty.
with driver("10.10.1.1", "admin", "secret") as dev:
dev.load_merge_candidate(config="interface Loopback42\n description napt-lab\n")
diff = dev.compare_config()
print(diff)
# TODO: if diff: dev.commit_config() else dev.discard_config()Goal: Use get_bgp_neighbors() and assert every neighbour in the default VRF is 'up'.
with driver("10.10.1.1", "admin", "secret") as dev:
bgp = dev.get_bgp_neighbors()
peers = bgp["global"]["peers"]
# TODO: for ip, data in peers.items(): assert data["is_up"], ipCurated videos & guides
Checklist โ pitfalls, gotchas & verification
Common pitfalls
- โขForgetting to close the session โ use a context manager so locked candidate configs are released.
- โขUsing load_replace_candidate before you trust your full templates can wipe unmanaged config.
- โขAssuming all getters exist on every platform โ support varies per driver.
Gotchas to watch
- โขcompare_config() returns an empty string when there is no change โ check truthiness, not None.
- โขSome platforms need optional_args (e.g. transport, secret) for enable mode.
- โขcommit_config() behaviour differs: JunOS is truly atomic, IOS emulates it.
Verify it works
- โขRun get_facts() first to confirm the connection and driver are correct.
- โขDiff every change with compare_config() before commit.
- โขAfter commit, re-read the relevant getter (e.g. get_interfaces_ip) to confirm the new state.
NAPALM shines as a config and state abstraction layer. Pair it with Nornir to run the same getters and commits across a whole inventory in parallel.