Skip to content

Multivendor configuration with NAPALM

The Python article introduced NAPALM as "one API across vendors" and turning CLI output into structured data used its getters to read facts with a fixed schema. This article picks up the other half of NAPALM's job — changing configuration — and the small, disciplined workflow that makes it safe: stage a candidate, review the diff, then commit or discard, with a rollback if a committed change turns out to be wrong.

One API, many vendors

NAPALM (Network Automation and Programmability Abstraction Layer) wraps each platform's native config mechanism behind the same method names, so a script written against the abstraction runs unchanged on IOS XE, Arista EOS, Juniper Junos, Cisco NX-OS, and IOS XR. You pick a driver by name and open a connection:

from napalm import get_network_driver

driver = get_network_driver("ios")          # eos, junos, nxos, iosxr, ...
device = driver(hostname="192.0.2.11",
                username="automation",
                password=PASSWORD)           # from the environment, never hard-coded
device.open()
# ... work ...
device.close()

That uniformity is the point: the same commit_config() call drives a Junos commit on one box and a configure replace on another, and your automation doesn't need to know which. Where NAPALM differs from pushing CLI lines with Netmiko is that it treats a config change as a transaction you preview before you apply, not a stream of commands sent blind.

The candidate-config workflow

Every NAPALM config change follows the same three steps: load a candidate, compare it against what's running, then commit it or discard it. The candidate is staged — nothing touches the device until you commit.

device.open()
try:
    device.load_merge_candidate(filename="vlan10.cfg")
    diff = device.compare_config()           # preview before anything is applied
    if diff:
        print(diff)
        device.commit_config()               # apply the staged change
    else:
        device.discard_config()              # nothing to do; drop the candidate
finally:
    device.close()

load_merge_candidate (and its sibling load_replace_candidate) accept either a filename= or an inline config= string. compare_config returns a unified diff of the candidate versus the running config as a string — empty when the candidate matches the device, which is your built-in idempotency check: no diff, nothing to commit. commit_config applies the candidate; discard_config throws it away without touching the device. Reviewing the diff before committing is the habit that makes this safe — it is the same stage-and-review gate that Terraform's plan gives you, applied to a live device.

Merge versus replace

The choice of loader decides what "configured" means, and it is the most important decision in the whole workflow.

Loader Behaviour Result
load_merge_candidate Additive. Applies the lines in the candidate on top of the running config. Anything not mentioned is left untouched.
load_replace_candidate Declarative. Makes the running config match the candidate exactly. Anything in the candidate but not on the device is added; anything on the device but not in the candidate is removed.

Merge is the right tool when you own one slice of the config — add three VLANs, adjust one interface — and want to leave everything else alone. Replace is what you want when the candidate is the complete intended state for the device, rendered from a source of truth: it converges the box to exactly that and deletes drift, which is far stronger than merge but also far more dangerous if the candidate is incomplete.

One caveat worth knowing before you lean on the diff: for a replace, compare_config produces a real line-by-line diff of what will change. For a merge, the diff is much simpler — on several platforms it reflects little more than the candidate lines themselves, because the device computes the delta only at commit time. Treat a merge diff as "here is what I'm adding," not "here is the net effect on the device," and check the per-platform caveats for your driver.

Undoing a committed change

Preview catches most mistakes before they land. For the ones that only show up after the change is live — a session that drops, a neighbour that won't come up — NAPALM gives you two safety nets.

rollback() reverts the most recent commit, restoring the configuration that was running before it. On IOS XE this is backed by the config-archive / configure replace machinery; on Junos and NX-OS it maps to their native rollback. It is the manual "undo the last change" you reach for when validation fails after a commit:

device.load_replace_candidate(filename="intended.cfg")
print(device.compare_config())
device.commit_config()

if not change_is_healthy():      # your pyATS / ping / API check
    device.rollback()            # back to the pre-commit config

Commit-confirm automates that revert so a change that cuts your own management session doesn't strand the device. commit_config(revert_in=300) applies the change but schedules an automatic rollback in 300 seconds unless you call confirm_commit() first — so if the box becomes unreachable, it heals itself without anyone logging in. Native commit-confirm support varies by platform (Junos and NX-OS have it directly), so confirm it is available for your driver before depending on it. Pair either mechanism with change validation using pyATS to decide, from data, whether to confirm or roll back.

Writing it safely

NAPALM removes the fragile parts of pushing config, but the secure coding practices from the rest of this site still apply:

  • Keep credentials out of the code. Read the username and password from the environment or a secrets store, exactly as you would for Netmiko or requests.
  • Always close the session. Wrap open()/close() in try/finally (as above) so a failure mid-change doesn't leak a connection or leave a candidate loaded.
  • Prefer replace from a source of truth. A full declarative push that converges the device is idempotent and self-correcting; a stack of merges drifts over time.
  • Canary, then batch. When you fan a change out with concurrency, prove it on one device first — a bad replace committed to the whole fleet at once is exactly the blast radius you don't want.

Key takeaways

  • NAPALM wraps each vendor's native config mechanism behind one API, so the same stage → diff → commit code runs across IOS XE, EOS, Junos, NX-OS, and IOS XR.
  • The workflow is always load a candidate → compare_config → commit_config or discard_config. Nothing touches the device until you commit, and an empty diff is your idempotency check.
  • load_merge_candidate is additive; load_replace_candidate is declarative and removes drift — stronger and riskier. The merge diff is simplistic on many platforms; the replace diff is exact.
  • rollback() undoes the last commit, and commit_config(revert_in=…) + confirm_commit() give you commit-confirm so a change that breaks reachability reverts itself. Commit-confirm support is platform-dependent.
  • Credentials from the environment, try/finally around the session, replace-from-source-of-truth, and canary-before-batch keep multivendor config changes safe at scale.

Sources: NAPALM documentation, NAPALM — changing the configuration, NAPALM supported devices and caveats, NAPALM on GitHub.