Sitemap
AshJo

AshJo

Passionate about Applied AI. Network Automation Engineer by day. Aspiring technical content creator after work hours. I speak and breathe Python.

Containerlab — QuickStart ft. pyATS

4 min readFeb 11, 2026

Container lab is a network device/topology simulation tool

https://containerlab.dev/

Installation:

curl -sL https://containerlab.dev/setup | sudo -E bash -s “all”

It starts with a simple topology file like <toponame>.clab.yaml

# topology documentation: http://containerlab.dev/lab-examples/srl-ceos/
name: srlceos01

topology:
nodes:
srl:
kind: nokia_srlinux
image: ghcr.io/nokia/srlinux:24.10
ceos:
kind: arista_ceos
image: ceos:4.32.0F

links:
- endpoints: ["srl:ethernet-1/1", "ceos:eth1"]

We have a Nokia Router and Arista Router as “docker” images but we need to have these images on our local development box where we going to spin up containerlab on.

For Nokia Image we need to auth with “ghcr.io”

Username is your git username and Password is your PAT.

how to get PAT — https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/How-to-create-a-GitHub-Personal-Access-Token-example


docker login ghcr.io
Authenticating with existing credentials… [Username: USERNAME]
i Info → To login with a different account, run 'docker logout' followed by 'docker login'
Login did not succeed, error: Error response from daemon: Get "https://ghcr.io/v2/": denied: denied
Username (USERNAME): ashwinjo
Password:
WARNING! Your credentials are stored unencrypted in '/home/ubuntusand/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
Login Succeeded

For Arista Image we will open an Arista Account and then go to downloads

Refer: https://www.youtube.com/watch?v=oCRE-vLSdwM

No lets deploy the topology yaml I showed you previously

clab deploy -t <toponame>.clab.yaml


clab deploy -t newtopo.clab.yaml
13:51:50 INFO Containerlab started version=0.72.0
13:51:50 INFO Parsing & checking topology file=newtopo.clab.yaml
13:51:50 INFO Creating lab directory path=/home/ubuntusand/clab-srlceos01
13:51:50 INFO Creating container name=ceos
13:51:50 INFO Creating container name=srl
13:51:52 INFO Running postdeploy actions for Arista cEOS 'ceos' node
13:51:52 INFO Created link: srl:e1-1 (ethernet-1/1) ▪┄┄▪ ceos:eth1
13:51:52 INFO Running postdeploy actions kind=nokia_srlinux node=srl
13:53:12 INFO Adding host entries path=/etc/hosts
13:53:12 INFO Adding SSH config for nodes path=/etc/ssh/ssh_config.d/clab-srlceos01.conf
13:53:12 INFO containerlab version
🎉=
│ A newer containerlab version (0.73.0) is available!
│ Release notes: https://containerlab.dev/rn/0.73/
│ Run 'clab version upgrade' or see https://containerlab.dev/install/ for other installation options.
╭─────────────────────┬─────────────────────────────┬─────────┬───────────────────╮
│ Name │ Kind/Image │ State │ IPv4/6 Address │
├─────────────────────┼─────────────────────────────┼─────────┼───────────────────┤
│ clab-srlceos01-ceos │ arista_ceos │ running │ 172.20.20.2 │
│ │ ceos:4.32.0F │ │ 3fff:172:20:20::2 │
├─────────────────────┼─────────────────────────────┼─────────┼───────────────────┤
│ clab-srlceos01-srl │ nokia_srlinux │ running │ 172.20.20.3 │
│ │ ghcr.io/nokia/srlinux:24.10 │ │ 3fff:172:20:20::3 │
╰─────────────────────┴─────────────────────────────┴─────────┴───────────────────╯

We got our 2 devices with as “docker” containers running locally.

Get AshJo’s stories in your inbox

Join Medium for free to get updates from this writer.

Now, lets login to confirm management connectivity:

ssh admin@clab-srlceos01-ceos
Warning: Permanently added 'clab-srlceos01-ceos' (ED25519) to the list of known hosts.
(admin@clab-srlceos01-ceos) Password:
ceos>show version
Arista cEOSLab
Hardware version:
Serial number: 4BEEC87BBD81469D73AEE36639A2EF13
Hardware MAC address: 001c.730d.c71c
System MAC address: 001c.730d.c71c

Software image version: 4.28.13M-40288711.42813M (engineering build)
Architecture: x86_64
Internal build version: 4.28.13M-40288711.42813M
Internal build ID: 4ddcb0e5-aafc-411a-8791-25167139c959
Image format version: 1.0
Image optimization: None

cEOS tools version: 1.1
Kernel version: 6.5.0-41-generic

Uptime: 1 minute
Total memory: 8113488 kB
Free memory: 2361048 kB

Let’s visualize the topology now

clab graph -t newtopo.clab.yaml
13:56:04 INFO Parsing & checking topology file=newtopo.clab.yaml
13:56:04 INFO Serving topology graph
addresses=
│ http://172.20.0.1:50080
│ http://10.36.229.249:50080
│ http://172.19.0.1:50080
│ http://172.17.0.1:50080
│ http://172.18.0.1:50080
│ http://172.22.0.1:50080
│ http://172.21.0.1:50080
│ http://172.23.0.1:50080
│ http://172.20.20.1:50080
│ http://172.31.255.1:50080
│ http://[3fff:172:20:20::1]:50080
Press enter or click to view image in full size

Detailed tutorial at : https://juliopdx.com/2021/12/10/my-journey-and-experience-with-containerlab/

Integration of clab with pyATS

I wanted to run everything I run on a real router on clab devices however I found that only .execute and .ping work. Genie based .parse() and learn() fail

As per Gemini which makes sense

This is highly expected and a very common “growing pain” for NetDevOps engineers. While pyATS is the gold standard for testing, it was born in a world of physical chassis and heavy Virtual Machines (CML/VIRL). Containerlab (clab) uses containerized NOS (Network Operating Systems) which behave quite differently at the terminal level.

Using pyATS to “execute” commands on clab device

testbed.yaml

testbed:
name: srlceos01

devices:
ceos:
os: eos
type: switch
connections:
cli:
protocol: ssh
ip: 172.20.20.2
port: 22
credentials:
default:
username: admin
password: admin

Script

from genie.testbed import load

# Load the testbed YAML
testbed = load('testbed.yaml')
print(f"Loaded testbed: {testbed.name}")


from genie.testbed import load

testbed = load('testbed.yaml')
device = testbed.devices['ceos']

# We connect to 'any' state.
# This bypasses the 'enable' service entirely, avoiding the |-S regex bug.
device.connect(
init_exec_commands=[],
init_config_commands=[],
log_stdout=False,
)

print("--- Connection Successful ---")
print(device.execute('show ip interface brief'))
print("-----------------------------")
print(device.ping("127.0.0.01"))

```
--- Connection Successful ---
Address
Interface IP Address Status Protocol MTU Owner
--------------- ------------------- ----------- ------------- --------- -------
Management0 172.20.20.2/24 up up 1500
-----------------------------
ping 127.0.0.01
PING 127.0.0.01 (127.0.0.1) 72(100) bytes of data.
80 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.040 ms
80 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.017 ms
80 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.014 ms
80 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.013 ms
80 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.013 ms

--- 127.0.0.01 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.013/0.019/0.040/0.011 ms, ipg/ewma 0.042/0.029 ms
```

I hope you leart something new today. Feel free to share this with your friends

--

--

AshJo
AshJo

Written by AshJo

Passionate about Applied AI. Network Automation Engineer by day. Aspiring technical content creator after work hours. I speak and breathe Python.