NAPALM – Network Automation with Python

1. What is NAPALM?

NAPALM stands for Network Automation and Programmability Abstraction Layer with Multivendor support. It is an open-source Python library that provides a unified API to interact with network devices from multiple vendors — Cisco IOS, Cisco IOS-XE, Cisco NX-OS, Arista EOS, Juniper JunOS, and more — using the same Python code regardless of the underlying vendor or operating system.

Without NAPALM, a network engineer would need to write completely separate automation scripts for each vendor — one script for Cisco, another for Arista, another for Juniper. NAPALM eliminates this duplication by providing a single consistent set of methods that work across all supported platforms.

NAPALM PropertyValue
Full nameNetwork Automation and Programmability Abstraction Layer with Multivendor support
TypeOpen-source Python library
Python versionPython 3.7+ (recommended: 3.8 or higher)
Installpip install napalm
GitHubgithub.com/napalm-automation/napalm
Key capabilityRead facts, retrieve operational data, push configurations, validate device state — across multiple vendors with one API
IntegrationsAnsible (napalm collection), Nornir, Salt, Netbox
Relation to other toolsComplements Netmiko (SSH commands) and RESTCONF/NETCONF (API-based) — NAPALM often uses Netmiko internally for IOS devices
Real-world analogy:
Think of NAPALM as a universal remote control for network devices. Just as a universal remote uses one set of buttons to control TVs from Sony, Samsung, and LG, NAPALM uses one set of Python methods to manage routers and switches from Cisco, Arista, and Juniper.

Related pages: Network Automation Overview | Python for Networking | Python Scripts | Netmiko | Ansible Overview | NETCONF & RESTCONF | REST API Overview | SSH Overview

2. Why Use NAPALM? The Multi-Vendor Problem

Enterprise networks rarely run a single vendor. A typical network might have Cisco IOS routers, Arista switches in the data centre, and Juniper firewalls at the perimeter. Managing all of these with vendor-specific scripts creates a maintenance nightmare. NAPALM solves this with a driver model — each vendor has a driver that translates NAPALM's generic methods into vendor-specific commands or API calls behind the scenes.

Without NAPALMWith NAPALM
Separate script per vendorOne script works across all vendors
Learn vendor-specific show command syntaxCall get_facts() on any device
Parse unstructured CLI text output manuallyReceive structured Python dictionaries
Config push method differs per platformSame load_merge_candidate() or load_replace_candidate() call
No built-in diff before applying changesBuilt-in compare_config() shows diff before commit
No rollback mechanismBuilt-in discard_config() and rollback()
Validation requires custom scriptsBuilt-in compliance_report() validates device state
Key insight: NAPALM does not replace Netmiko or SSH — for IOS devices it actually uses Netmiko under the hood. What NAPALM adds is the abstraction layer — the same Python method works whether the device underneath uses SSH+CLI, NETCONF, or a REST API.

3. Supported Drivers (Platforms)

NAPALM uses drivers to communicate with different platforms. Each driver knows how to connect to its target OS and translate NAPALM method calls into the correct vendor commands or API calls. You specify the driver when opening a connection.

Driver Name Platform Connection Method Notes
ios Cisco IOS / IOS-XE SSH via Netmiko Most widely used driver; covers classic IOS routers and Cat9k switches
iosxr Cisco IOS-XR SSH via Netmiko Service-provider routers (ASR9k, NCS)
nxos Cisco NX-OS NX-API (HTTP/JSON) Nexus data-centre switches; requires NX-API enabled
nxos_ssh Cisco NX-OS SSH via Netmiko Fallback driver for Nexus when NX-API is unavailable
eos Arista EOS eAPI (HTTPS/JSON) Requires management api http-commands on the device
junos Juniper JunOS NETCONF via ncclient Routers and switches (SRX, MX, QFX); requires NETCONF enabled
  NAPALM Driver Architecture:

  Your Python Script
        |
        | napalm.get_network_driver('ios')
        ↓
  ┌─────────────────────────────────────────┐
  │           NAPALM Abstraction Layer       │
  │  get_facts() / get_interfaces() /        │
  │  load_merge_candidate() / commit() ...   │
  └───────────┬────────────┬────────────────┘
              |            |
     ┌────────▼───┐   ┌────▼──────┐   ┌─────────────┐
     │  IOS driver │   │ EOS driver│   │ JunOS driver │
     │  (Netmiko)  │   │ (eAPI)    │   │ (NETCONF)    │
     └────────┬───┘   └────┬──────┘   └──────┬───────┘
              |             |                  |
         Cisco IOS      Arista EOS        Juniper JunOS
            

Related pages: NETCONF & RESTCONF | REST API Overview | JSON, XML & YANG

4. Installation and Opening a Connection

NAPALM is installed with pip. Once installed, every interaction follows the same three-step pattern: get the driver → open the connection → call methods → close.

# ── Step 1: Install NAPALM ───────────────────────────────────────────────
pip install napalm

# ── Step 2: Import and select the driver ─────────────────────────────────
import napalm

driver = napalm.get_network_driver('ios')   # returns the IOS driver class

# ── Step 3: Create a device object ───────────────────────────────────────
device = driver(
    hostname='192.168.1.1',
    username='admin',
    password='cisco123',
    optional_args={
        'port': 22,                 # SSH port (default: 22)
        'secret': 'enable_secret',  # enable secret if required
        'global_delay_factor': 2    # slow devices need more time
    }
)

# ── Step 4: Open the connection ───────────────────────────────────────────
device.open()

# ── Step 5: Do your work (call getters, push config, etc.) ───────────────
# ... (see next sections)

# ── Step 6: Always close the connection ──────────────────────────────────
device.close()

Using a Context Manager (Recommended)

Using Python's with statement ensures the connection is always closed cleanly — even if an exception occurs — and is the recommended pattern for production scripts.

import napalm

driver = napalm.get_network_driver('ios')

with driver(hostname='192.168.1.1', username='admin', password='cisco123') as device:
    facts = device.get_facts()
    print(facts)
# Connection is automatically closed when the 'with' block exits
Best practice: Never hard-code credentials in scripts. Use environment variables, a secrets vault, or a configuration file with restricted permissions. For example: password=os.environ.get('DEVICE_PASSWORD').

5. NAPALM Getters — Reading Device State

Getters are NAPALM methods that retrieve operational data from a device and return it as a structured Python dictionary — no manual CLI parsing required. Every getter returns the same dictionary structure regardless of which vendor driver is being used.

Getter Method What It Returns Typical Use Case
get_facts()Hostname, vendor, model, OS version, serial number, uptime, interface listDevice inventory; asset management
get_interfaces()Interface name, enabled state, speed, MAC address, MTU, descriptionInterface audit; change verification
get_interfaces_ip()IP addresses and prefix lengths per interfaceIP address management; audit
get_interfaces_counters()TX/RX packets, errors, discards per interfacePerformance monitoring; error detection
get_bgp_neighbors()BGP peer state, AS numbers, received/sent prefixesBGP health checks; peering audits
get_arp_table()ARP entries: IP, MAC, interface, ageTroubleshooting; security audits
get_mac_address_table()MAC table: MAC, VLAN, interface, typePort security audit; device location
get_lldp_neighbors()LLDP neighbour hostname and port per local interfaceTopology discovery; documentation
get_ntp_servers()Configured NTP server addressesNTP compliance check
get_route_to()Best route(s) to a specific destination prefixRouting table verification
get_snmp_information()SNMP chassis ID, contact, location, community stringsSNMP compliance audit
get_config()Running, startup, or candidate configuration as a stringConfig backup; change management

Getter Examples — Real Python Code

import napalm

driver = napalm.get_network_driver('ios')

with driver(hostname='10.0.0.1', username='admin', password='cisco123') as device:

    # ── get_facts() ───────────────────────────────────────────────────────
    facts = device.get_facts()
    print(facts)
    # Output (dictionary):
    # {
    #   'hostname': 'R1',
    #   'fqdn': 'R1.lab.local',
    #   'vendor': 'Cisco',
    #   'model': 'CSR1000V',
    #   'os_version': 'Version 16.9.3',
    #   'serial_number': 'ABC123DEF456',
    #   'uptime': 86400,
    #   'interface_list': ['GigabitEthernet1', 'GigabitEthernet2', 'Loopback0']
    # }

    # ── get_interfaces_ip() ───────────────────────────────────────────────
    ip_info = device.get_interfaces_ip()
    print(ip_info)
    # Output:
    # {
    #   'GigabitEthernet1': {
    #     'ipv4': {'192.168.1.1': {'prefix_length': 24}}
    #   },
    #   'Loopback0': {
    #     'ipv4': {'10.0.0.1': {'prefix_length': 32}}
    #   }
    # }

    # ── get_bgp_neighbors() ───────────────────────────────────────────────
    bgp = device.get_bgp_neighbors()
    for peer_ip, peer_data in bgp['global']['peers'].items():
        state = peer_data['description']
        is_up = peer_data['is_up']
        print(f"Peer {peer_ip} — Up: {is_up}")

Related pages: Python Scripts for Networking | show ip interface brief | show ip route | ARP Table | BGP Overview

6. Configuration Management — Replace vs Merge

NAPALM provides two methods for pushing configuration changes to a device. Understanding the difference between replace and merge is critical — choosing the wrong one can cause unintended config changes or even outages.

Method How It Works When to Use Risk Level
load_merge_candidate() Adds or updates only the specified configuration lines. Existing config not mentioned in the candidate is left untouched. Adding new interfaces, BGP neighbours, ACL entries, or NTP servers without touching anything else Low — existing config is preserved
load_replace_candidate() Replaces the entire running configuration with the candidate. Any line in the running config that is not in the candidate is removed. Full configuration lifecycle management; golden config enforcement; when you want the device to exactly match a source-of-truth file High — removes any config not in the candidate file
  Merge (safe, additive):                Replace (destructive, absolute):

  Running config:  A, B, C              Running config:  A, B, C
  Candidate:       D, E                 Candidate:       A, D, E
                   ↓                                     ↓
  Result:          A, B, C, D, E        Result:          A, D, E
                                        (B and C are REMOVED)
            

Config Push Workflow — Always Diff Before Commit

import napalm

driver = napalm.get_network_driver('ios')

with driver(hostname='10.0.0.1', username='admin', password='cisco123') as device:

    # ── Load a merge candidate from a file ────────────────────────────────
    device.load_merge_candidate(filename='ntp_config.txt')
    # Contents of ntp_config.txt:
    # ntp server 10.10.10.1
    # ntp server 10.10.10.2

    # ── Always compare BEFORE committing ─────────────────────────────────
    diff = device.compare_config()
    if diff:
        print("Pending changes:")
        print(diff)
        # Output shows unified diff — lines starting with + are additions,
        # lines starting with - are removals (like 'git diff')

        # ── Commit only if the diff looks correct ─────────────────────────
        confirm = input("Commit these changes? (yes/no): ")
        if confirm.lower() == 'yes':
            device.commit_config()
            print("Configuration committed successfully.")
        else:
            device.discard_config()   # Roll back the candidate — nothing applied
            print("Changes discarded.")
    else:
        print("No changes detected — device already has this configuration.")
        device.discard_config()
import napalm

driver = napalm.get_network_driver('ios')

with driver(hostname='10.0.0.1', username='admin', password='cisco123') as device:

    # ── Load a REPLACE candidate (full config file) ───────────────────────
    device.load_replace_candidate(filename='golden_config_R1.txt')

    diff = device.compare_config()
    print("Replace diff (lines to be REMOVED start with -, ADDED with +):")
    print(diff)

    # ── Rollback if something goes wrong post-commit ──────────────────────
    device.commit_config()
    # If the device becomes unreachable after commit:
    # device.rollback()   # Restores the previous running config
Atomic commits: On platforms that support it (Juniper JunOS, Arista EOS), NAPALM's commit_config() is atomic — the entire change applies or nothing does. On Cisco IOS, config is applied line by line, so partial application is possible if an error occurs mid-way. Always test on non-production devices first.

7. Compliance Validation with NAPALM

NAPALM's validation feature lets you define the expected state of a device in a YAML file and then automatically compare it against the device's actual state. It returns a compliance report — ideal for post-change verification, audits, and continuous compliance checking in a CI/CD pipeline.

Validation File (YAML)

# validation.yml — defines expected device state
---
- get_facts:
    hostname: R1
    vendor: Cisco
    os_version:
      _mode: contains
      _value: "16.9"

- get_interfaces:
    GigabitEthernet1:
      is_up: true
      is_enabled: true
    GigabitEthernet2:
      is_up: true

- get_bgp_neighbors:
    global:
      peers:
        10.0.0.2:
          is_up: true
          remote_as: 65002

Running the Compliance Check

import napalm

driver = napalm.get_network_driver('ios')

with driver(hostname='10.0.0.1', username='admin', password='cisco123') as device:

    # Run compliance report against the validation YAML file
    report = device.compliance_report('validation.yml')

    # Print results
    import json
    print(json.dumps(report, indent=2))

    # Check overall pass/fail
    if report['complies']:
        print("✅ Device is COMPLIANT with the validation policy.")
    else:
        print("❌ Device is NON-COMPLIANT — review the report above.")

# Example output structure:
# {
#   "complies": false,
#   "get_facts": {
#     "complies": true,
#     "skipped": []
#   },
#   "get_bgp_neighbors": {
#     "complies": false,
#     "skipped": [],
#     "extra": [],
#     "missing": ["10.0.0.2"]
#   }
# }

Related pages: Network Baseline | JSON, XML & YANG | Python Scripts

8. Multi-Device Automation — Inventory Loop

The real power of NAPALM comes when you run it against multiple devices at once. By combining NAPALM with a simple Python inventory list (or an external inventory tool like Nornir or Ansible), you can collect facts, audit configs, or push changes across your entire network in seconds.

Example — Collect Facts from All Devices

import napalm
import json

# Define your device inventory
inventory = [
    {'host': '10.0.0.1', 'driver': 'ios',   'username': 'admin', 'password': 'cisco123'},
    {'host': '10.0.0.2', 'driver': 'eos',   'username': 'admin', 'password': 'arista123'},
    {'host': '10.0.0.3', 'driver': 'junos', 'username': 'admin', 'password': 'juniper123'},
]

results = {}

for device_info in inventory:
    driver_class = napalm.get_network_driver(device_info['driver'])
    try:
        with driver_class(
            hostname=device_info['host'],
            username=device_info['username'],
            password=device_info['password']
        ) as device:
            facts = device.get_facts()
            results[device_info['host']] = {
                'hostname': facts['hostname'],
                'vendor':   facts['vendor'],
                'model':    facts['model'],
                'version':  facts['os_version'],
                'uptime':   facts['uptime'],
                'status':   'success'
            }
    except Exception as e:
        results[device_info['host']] = {'status': 'failed', 'error': str(e)}

# Print structured results
print(json.dumps(results, indent=2))

Example — Push NTP Config to All IOS Devices

import napalm

ios_devices = [
    {'host': '10.0.0.1', 'username': 'admin', 'password': 'cisco123'},
    {'host': '10.0.0.2', 'username': 'admin', 'password': 'cisco123'},
]

ntp_config = """
ntp server 10.10.10.1 prefer
ntp server 10.10.10.2
"""

driver = napalm.get_network_driver('ios')

for dev in ios_devices:
    with driver(hostname=dev['host'], username=dev['username'], password=dev['password']) as device:
        device.load_merge_candidate(config=ntp_config)
        diff = device.compare_config()
        if diff:
            device.commit_config()
            print(f"[{dev['host']}] ✅ NTP config applied.")
        else:
            device.discard_config()
            print(f"[{dev['host']}] ℹ️  Already configured — no changes needed.")

Related pages: Ansible Overview | Postman & Ansible | NTP Overview | NTP Sync | NAPALM Multi-Vendor Lab

9. NAPALM vs Other Network Automation Tools

Tool What It Does Best For Multi-Vendor?
NAPALM Unified Python API — getters, config push, validation Structured data retrieval; config lifecycle management; compliance checks ✅ Yes — core purpose
Netmiko SSH library — sends CLI commands and returns raw text output Running arbitrary show commands; one-off CLI tasks; platforms NAPALM doesn't support ✅ Yes (40+ platforms)
Ansible Agentless automation framework — YAML playbooks, large task orchestration Large-scale deployments; config management pipelines; CI/CD integration ✅ Yes (via napalm collection and vendor modules)
NETCONF / ncclient XML-based network management protocol over SSH Fine-grained config manipulation on NETCONF-capable devices ✅ Yes (IETF standard)
RESTCONF REST API for YANG-modelled device data over HTTPS Programmatic config/state access on modern IOS-XE, NX-OS, EOS platforms ✅ Yes (IETF standard)
Nornir Pure-Python automation framework — often used as an Ansible alternative with NAPALM plugins Python-native orchestration; parallel task execution across large inventories ✅ Yes (via NAPALM/Netmiko plugins)
Typical combination in production:
Nornir (orchestration + parallelism) + NAPALM (structured data + config management) + Netmiko (fallback for CLI-only tasks) + Netbox (source of truth for inventory).

Related pages: Network Automation Overview | Ansible Overview | NETCONF & RESTCONF | REST API Overview | Northbound & Southbound APIs | Controller-Based Networking

10. Common NAPALM Errors and Fixes

ErrorCauseFix
ConnectionException Wrong hostname/IP, SSH not enabled, firewall blocking port 22 Verify SSH is enabled (ip ssh version 2); check reachability with ping; confirm port in optional_args
AuthenticationException Wrong username or password; AAA misconfiguration on device Test credentials manually with SSH; verify AAA/TACACS+ config on the device
MergeConfigException Syntax error in the candidate config file; IOS rejected one or more lines Validate config syntax locally; check the error message for the specific rejected line
ReplaceConfigException Platform does not support atomic config replace; or the candidate is missing critical lines (e.g. no management interface) Ensure the golden config includes all necessary lines including management access; test on a lab device first
Getter returns empty dict {} Feature not configured on the device (e.g. get_bgp_neighbors() on a non-BGP device) Confirm the feature is configured and active; check show ip bgp summary on the device manually
NX-OS ConnectionException NX-API not enabled on Nexus switch Run feature nxapi on the switch, or switch to the nxos_ssh driver
Arista EOS ConnectionException eAPI not enabled Run management api http-commands + no shutdown on the switch

Related pages: SSH | SSH vs Telnet Security | AAA Overview | Debug Commands | Troubleshooting Methodology

11. Key Points Summary

TopicKey Facts
What is NAPALMOpen-source Python library providing a unified multi-vendor API for network device management
Installpip install napalm — Python 3.7+ required
Driversios, iosxr, nxos, nxos_ssh, eos, junos — each translates NAPALM calls into vendor-specific commands or API calls
GettersMethods that return structured Python dicts: get_facts(), get_interfaces(), get_bgp_neighbors(), get_arp_table(), etc.
Mergeload_merge_candidate() — additive; existing config not in candidate is preserved
Replaceload_replace_candidate() — absolute; config not in candidate is removed. Use with caution.
Safe change workflowLoad candidate → compare_config() to review diff → commit_config() or discard_config()
Rollbackrollback() restores the previous config; discard_config() cancels an uncommitted candidate
Validationcompliance_report() compares device state against a YAML validation file — returns pass/fail per getter
Context managerUse with driver(...) as device: — guarantees connection is closed even if an exception occurs
vs NetmikoNetmiko sends CLI commands and returns raw text; NAPALM returns structured dicts and handles config management
vs AnsibleAnsible is an orchestration framework; NAPALM is a Python library — they are often used together

Practice Quiz – NAPALM

1. What does the acronym NAPALM stand for in the context of network automation?

Correct answer is B. NAPALM stands for Network Automation and Programmability Abstraction Layer with Multivendor support. The name captures its core purpose exactly — it creates an abstraction layer over multiple vendor platforms so that the same Python API works regardless of whether the underlying device is a Cisco IOS router, an Arista switch, or a Juniper firewall.

2. Which NAPALM driver would you use to connect to a Cisco IOS router?

Correct answer is C. The driver name for Cisco IOS and IOS-XE devices is simply 'ios'. You retrieve it with napalm.get_network_driver('ios'). Other driver names are 'eos' for Arista, 'junos' for Juniper, 'nxos' for Cisco Nexus via NX-API, and 'iosxr' for Cisco IOS-XR. Using an incorrect driver name raises a ModuleNotFoundError.

3. What does the NAPALM get_facts() getter return, and what makes it powerful?

Correct answer is A. get_facts() returns a structured Python dictionary with keys including hostname, vendor, model, os_version, serial_number, uptime, and interface_list. The power is that this dictionary has the same structure whether you run it against a Cisco IOS router, an Arista switch, or a Juniper device — no manual parsing of vendor-specific show version output required.

4. What is the critical difference between load_merge_candidate() and load_replace_candidate()?

Correct answer is D. load_merge_candidate() is additive — it only adds or modifies what you specify and leaves everything else intact. It is safe for incremental changes like adding an NTP server or a new BGP neighbour. load_replace_candidate() is absolute — it replaces the entire running config with the candidate. Any config line not present in the candidate file is removed. This makes replace very powerful for enforcing a golden config but very dangerous if the candidate file is incomplete.

5. What is the correct order of steps in a safe NAPALM config change workflow?

Correct answer is B. The safe workflow is: (1) load_merge_candidate() or load_replace_candidate() — stage the change without applying it; (2) compare_config() — view the diff of what will change; (3) review the diff carefully; (4) commit_config() to apply if correct, or discard_config() to cancel if something looks wrong. Never commit without reviewing the diff first — especially with load_replace_candidate().

6. What does device.compare_config() return, and why is it important?

Correct answer is C. compare_config() returns a unified diff string — lines beginning with + are additions to the running config, lines beginning with - are removals. If it returns an empty string, the running config already matches the candidate and no changes would be made. This is the most important step in the workflow — it lets you see exactly what will change before you commit, preventing accidental outages. Think of it as git diff but for network device configuration.

7. What is the purpose of NAPALM's compliance_report() method?

Correct answer is A. compliance_report() takes a YAML validation file that defines the expected state of the device (e.g. hostname should be R1, GigabitEthernet1 should be up, BGP peer 10.0.0.2 should be established). It then calls the relevant NAPALM getters to retrieve the actual state and compares them. The method returns a structured dictionary with a top-level complies boolean and per-getter detail showing exactly which values passed or failed. This is ideal for post-change verification and continuous compliance monitoring.

8. What is the key functional difference between NAPALM and Netmiko?

Correct answer is D. Netmiko is an SSH library — you send a CLI command like send_command('show interfaces') and get back a raw text string that you must parse yourself (often using TextFSM or Genie). NAPALM sits at a higher abstraction level — get_interfaces() returns a structured Python dictionary with consistent keys, no parsing needed. NAPALM also provides built-in config diff, commit, discard, and rollback capabilities. Importantly, NAPALM actually uses Netmiko internally to communicate with Cisco IOS devices.

9. Why is using a Python with statement (context manager) the recommended way to open a NAPALM connection?

Correct answer is B. Python's with statement is a context manager — it automatically calls the object's __exit__ method (which triggers device.close()) when the block finishes, whether that is because the code completed normally or because an exception was raised. Without a context manager, if your script raises an exception mid-way, device.close() is never called, leaving an orphaned SSH session open on the device. On devices with a limited VTY line count (e.g. IOS defaults to 5 VTY lines), unclosed sessions can eventually block all management access.

10. You run device.load_replace_candidate(filename='golden.txt') followed by device.compare_config() and see many lines starting with -. What does this mean and what should you do?

Correct answer is C. In a unified diff, lines beginning with - indicate content that will be removed from the device, and lines beginning with + indicate content that will be added. With load_replace_candidate(), seeing many - lines means your golden config file is missing configuration that currently exists on the device — those lines will be deleted when you commit. This is the most dangerous scenario in NAPALM. Always review every - line carefully. If the removals are unintended — for example, the golden file is missing management interface config — call device.discard_config() immediately to cancel the staged change without applying it.

← Back to Home