Your hands-on networking playground. Containerlab on Hetzner VPS with Nokia SR Linux, FRR, and Python automation.
# SSH into the lab
clab
# See topology
clab inspect
# Open topology web viewer
clab web
# Deploy the lab
clab deploy
# Destroy the lab
clab destroy| Node | Type | OS | Purpose | How to Access |
|---|---|---|---|---|
| router | Nokia SR Linux | SR Linux v26.3.2 | Core routing, NETCONF/YANG, VXLAN/EVPN | sr_cli (interactive CLI) |
| frr | Alpine Linux | FRR 10.5.3 | BGP/OSPF routing, Linux networking | vtysh or sh |
| h1 | Alpine Linux | Linux | Traffic generator, test host | sh |
| h2 | Alpine Linux | Linux | Traffic generator, test host | sh |
| toolkit | Alpine Linux | Python 3.8 | Automation scripts, YANG tools | python3 |
From your Mac, you can access any lab node directly:
# SR Linux CLI (like Cisco's enable mode)
clab "docker exec -it clab-network-lab-router sr_cli"
# Once in sr_cli:
/interface ethernet-1/1 admin-state enable
/interface ethernet-1/1 subinterface 0 ipv4 address 10.0.0.1/24
commit now
show version
info from state /interface ethernet-1/1
# Bash shell inside SR Linux
clab "docker exec -it clab-network-lab-router bash"
# From bash: ip addr show, ping, tcpdump, etc.
# FRR router - Alpine shell
clab "docker exec -it clab-network-lab-frr sh"
# From shell: vtysh (routing CLI), ip link, ping
# FRR routing CLI (vtysh - like Cisco IOS)
clab "docker exec -it clab-network-lab-frr vtysh"
# From vtysh:
# show ip route
# show bgp summary
# configure terminal
# router bgp 65000
# neighbor 10.0.0.2 remote-as 65000
# Hosts - Alpine shell
clab "docker exec -it clab-network-lab-h1 sh"
# From shell: ping, iperf3, curl, wget
# Toolkit - Python automation hub
clab "docker exec -it clab-network-lab-toolkit sh"
# From shell: python3, ncclient, pyang, netmiko# List all nodes
clab inspect
# Run one command on a specific node
clab "docker exec clab-network-lab-router sr_cli -c 'show version'"
clab "docker exec clab-network-lab-frr ip addr show"
# Ping between nodes
clab "docker exec clab-network-lab-h1 ping 10.0.0.10"
# View logs
clab "docker logs clab-network-lab-router"
# Live topology graph
clab web # Opens http://46.225.27.145:50080
# Check resource usage
clab "free -h && docker stats --no-stream"# Enter CLI
docker exec -it clab-network-lab-router sr_cli
# Show commands
show version
show /interface brief
show /system network-instance brief
show /system network-instance default protocol bgp neighbor
# Configure
/interface ethernet-1/1
admin-state enable
subinterface 0
ipv4 address 10.0.0.1/24
commit now
# Network instance (like VRF)
/network-instance default
interface ethernet-1/1.0
commit now
# Run Linux commands from sr_cli
bash
ip addr show
ping 10.0.0.1
exit
# Save/show config
save /tmp/config.json
info from running
SR Linux has NETCONF enabled by default on port 830. Run Python directly on the VPS host (ncclient + pyang already installed):
# SSH into VPS
clab
# Test NETCONF connection to SR Linux router
python3 << 'EOF'
from ncclient import manager
conn = manager.connect(
host="172.20.20.2", # router's IP on clab network
port=830,
username="admin",
password="NokiaSrl1!",
hostkey_verify=False,
allow_agent=False,
look_for_keys=False
)
print("NETCONF connected!")
print("Server capabilities:", len(conn.server_capabilities), "capabilities")
for cap in list(conn.server_capabilities)[:5]:
print(" ", cap)
result = conn.get(filter=('xpath', '/system/version'))
print(result.xml[:500])
conn.close_session()
EOF
# Get full running config as XML
python3 << 'EOF'
from ncclient import manager
m = manager.connect(host="172.20.20.2", port=830, username="admin",
password="NokiaSrl1!", hostkey_verify=False, allow_agent=False, look_for_keys=False)
result = m.get_config(source="running")
import xml.dom.minidom
print(xml.dom.minidom.parseString(result.xml).toprettyxml()[:3000])
m.close_session()
EOF
# Push a config change via NETCONF
python3 << 'EOF'
from ncclient import manager
m = manager.connect(host="172.20.20.2", port=830, username="admin",
password="NokiaSrl1!", hostkey_verify=False, allow_agent=False, look_for_keys=False)
config = '''
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<interface xmlns="urn:nokia.com:srlinux:naas:interfaces">
<ethernet-1/2>
<admin-state>enable</admin-state>
</ethernet-1/2>
</interface>
</config>
'''
reply = m.edit_config(target="candidate", config=config)
print("Edit:", reply.ok)
m.commit()
print("Commit: OK")
m.close_session()
EOFkind: srl
image: ghcr.io/nokia/srlinux:latest
type: ixr-d2l
No account needed. Just add to any topology.
- Register at https://www.arista.com/en/user-registration
- Log in to https://www.arista.com, download cEOS image
- Load into Docker:
docker import cEOS-lab.tar.xz ceos:latest - Add to topology:
leaf-arista:
kind: ceos
image: ceos:latest- Register at https://www.juniper.net/us/en/user-registration
- Download vJunos image from Juniper support
- Load into Docker:
docker load -i vjunos-switch.img - Add to topology:
leaf-junos:
kind: juniper_vjunosswitch
image: juniper/vjunos-switch:latest- Register at https://cisco.com
- Download IOS-XRv image
- Use vrnetlab to wrap it:
leaf-xr:
kind: vr-xrv9k
image: vrnetlab/vr-xrv9k:700leaf-sonic:
kind: linux
image: sonic:latestBuild your own Docker image from https://github.com/sonic-net/SONiC
# Enter FRR CLI
clab "docker exec -it clab-network-lab-frr vtysh"
# Configure BGP EVPN with VXLAN
configure terminal
router bgp 65000
bgp router-id 10.0.0.2
neighbor 10.0.0.1 remote-as 65000
address-family l2vpn evpn
neighbor 10.0.0.1 activate
advertise-all-vni
exit-address-family
exit
# Create VXLAN interface
ip link add vxlan10 type vxlan id 10 dstport 4789 local 10.0.0.2 nolearning
ip link set vxlan10 up
ip addr add 192.168.10.1/24 dev vxlan10# Enter SR Linux CLI
clab "docker exec -it clab-network-lab-router sr_cli"
# Configure BGP EVPN
/network-instance default
protocols bgp
group evpn-peers
admin-state enable
peer-as 65000
local-as 65000
transport local-address 10.0.0.1
neighbor 10.0.0.2
peer-group evpn-peers
# Configure VXLAN interface
/tunnel-interface vxlan1
type vxlan
vxlan
vni 10
ingress-src-ip 10.0.0.1
commit nowThe VPS itself is provisioned via Terraform:
cd ~/Documents/network-lab/terraform
# View plan
terraform plan -var="hcloud_token=$HCLOUD_TOKEN"
# Apply (provision)
terraform apply -var="hcloud_token=$HCLOUD_TOKEN"
# Tear down (DANGER - deletes the VPS)
terraform destroy -var="hcloud_token=$HCLOUD_TOKEN"Infrastructure specs:
- Server type: CX23 (2 vCPU, 4 GB RAM, ~€5.80/mo)
- Location: nbg1 (Nuremberg)
- Image: Ubuntu 24.04
- Cloud-init: Auto-installs Docker, containerlab, Python stack
- Labels:
purpose=network-automation-lab - Protection: delete_protection=true (prevents accidental destruction)
To upgrade the server (e.g., to CX33 for more devices):
# In terraform/variables.tf, change:
variable "server_type" {
default = "cx33" # 4 vCPU, 8 GB RAM, ~€9.30/mo
}
# Then: terraform apply~/Documents/network-lab/
├── labs/
│ ├── 00-ospf-basics/ # Alpine + manual routing
│ └── 01-core-lab/ # SR Linux + FRR (current)
│ └── 02-evpn-lab/ # BGP EVPN + VXLAN (future)
│ └── 03-yang-lab/ # YANG/NETCONF deep dive (future)
├── terraform/
│ ├── main.tf # Hetzner server resource
│ ├── variables.tf # Configurable variables
│ ├── outputs.tf # Server IP, SSH command
│ └── cloud-init.yaml # Bootstrapping script
├── automation/ # Python scripts (future)
├── docs/ # Session notes, reference
├── Makefile # All commands
└── README.md # This file
cd ~/Documents/network-lab
make help # List all commands
make deploy # Deploy the core lab
make cleanup # Destroy the lab
make inspect # Show running nodes
make install-all # Install local tools
make pull-images # Pull container images
make tf-plan # Terraform plan
make tf-apply # Terraform apply
make tf-destroy # Terraform destroy
make tf-ssh # SSH into the VPS
make tf-sync # Sync labs to VPS| Lab Size | Nodes | Memory | RAM on CX23 | Works? |
|---|---|---|---|---|
| Minimal (current) | 1 SRL + 1 FRR + 2 hosts + toolkit | ~1.2 GB | 4 GB | ✅ Yes |
| Medium | 3 SRL + 3 FRR + hosts | ~3.0 GB | 4 GB | |
| Large (EVPN) | 1 spine + 2 leaf SRL + 4 hosts | ~2.8 GB | 4 GB |
For larger labs, upgrade to CX33: edit terraform/variables.tf and change server_type to cx33, then make tf-apply.
This lab is fully accessible from Hermes Agent via MCP (Model Context Protocol). You can manage the entire lab in natural language.
Already configured in ~/.hermes/config.yaml — just ask:
How many nodes are running in my lab?
Show me the SR Linux version
NETCONF get the interface config
What's the lab health status?
lab-cli topology # List running nodes
lab-cli router_cmd 'show version' # SR Linux CLI
lab-cli frr_cmd 'show ip route' # FRR CLI
lab-cli netconf_get # Full config via NETCONF
lab-cli lab_status # System health + reachability
lab-cli netconf_get '<interface xmlns="urn:nokia:srlinux:netw:if"/>' # Filtered NETCONFOpen in browser: http://46.225.27.145:8080
Or via the shortcut:
clab dashboard # Opens in browserThe dashboard provides:
- Topology view — live node cards with IPs and status (auto-refresh every 15s)
- Command Runner — SR Linux CLI, FRR CLI, host shell, all from the browser
- NETCONF Playground — full NETCONF get/edit on SR Linux with preset filters
- Lab Status — system resources, device reachability, software versions
Auto-starts on VPS boot via systemd service.
You (Hermes chat / CLI) ── SSH ── VPS MCP Server ── Docker exec/NETCONF ── Lab Nodes
├─ Web Dashboard (port 8080)
└─ Topology Graph (port 50080)
The MCP server lives on the VPS and exposes 7 tools. Hermes connects via SSH stdio transport — zero dependencies on the Mac side.
# Dashboard (systemd)
clab "systemctl status network-lab-dashboard"
# Topology graph
clab graph # Starts on port 50080
# MCP server (auto via Hermes config)
clab mcp # List available toolsRepo: https://github.com/Radmanded/network-lab
The Terraform configs, lab topologies, and this guide are all version-controlled. Clone anywhere:
git clone https://github.com/Radmanded/network-lab
cd network-lab
# Edit terraform/variables.tf → set hcloud_token
make tf-apply