The Thinking Network (Installment 4): Containerlab & The Fabric Deploy

Installment 4 of The Thinking Network. We execute the build script, using Containerlab to dynamically deploy a carrier-grade SR Linux network topology in thirty seconds.

The Thinking Network (Installment 4): Containerlab & The Fabric Deploy

Architecture Overview: Phase 4Objective: Programmatically deploy a virtualized carrier-grade network fabric.Core Technologies: Containerlab, Python automation, Nokia SR Linux, and YAML topology generation.The Goal: Transform a single configuration file into 13 active containers, establishing the physical diamond topology required for AI traffic engineering.

The Deploy

The previous installments covered what this lab is and why it exists. This one is where it starts running.

One command:

python3 phase3_topology.py

Thirty seconds later, thirteen containers are running on a laptop. Four Nokia SR Linux routers. Four Linux client workloads. A full telemetry and logging stack. A diamond fabric that did not exist before that command ran.
This installment is about what that command actually does - and what it would take to do it by hand.

I prefer to build my labs and live networks using scripts because it allows for faster spin-ups. When I am deploying a 40-node Metro-Ethernet environment in the field, scripts are what reduce the errors. The lab should reflect that reality.


What ContainerLab Is

ContainerLab is an open-source tool built by Nokia and the network engineering community for running containerized network labs. It reads a topology file, creates the containers, connects them with virtual network interfaces, assigns management IPs, and brings everything up in the correct order.

The topology file is called lab.yml. It is not written by hand. The Python script generates it from lab_config.py - the single source of truth for everything in this lab. Node definitions, management IPs, link connections, container images, port bindings, environment variables - all of it derives from one file.

This matters because it means changing one value in lab_config.py changes everything that depends on it. The topology file, the router configs, the Prometheus scrape config, the Grafana datasource. Nothing is hardcoded twice.


What the Topology File Defines

The generated lab.yml describes thirteen nodes:

The diamond fabric - four Nokia SR Linux routers:

srl1:
  kind: nokia_srlinux
  mgmt-ipv4: 172.20.20.11
  startup-config: ../configs/fabric/srl1.cfg

srl2:
  kind: nokia_srlinux
  mgmt-ipv4: 172.20.20.12
  startup-config: ../configs/fabric/srl2.cfg

srl3:
  kind: nokia_srlinux
  mgmt-ipv4: 172.20.20.13
  startup-config: ../configs/fabric/srl3.cfg

srl4:
  kind: nokia_srlinux
  mgmt-ipv4: 172.20.20.14
  startup-config: ../configs/fabric/srl4.cfg

Four client workloads - Linux containers that generate traffic through the fabric:

client-l2-1:
  kind: linux
  mgmt-ipv4: 172.20.20.31

client-l2-2:
  kind: linux
  mgmt-ipv4: 172.20.20.32

client-l3-1:
  kind: linux
  mgmt-ipv4: 172.20.20.33

client-l3-2:
  kind: linux
  mgmt-ipv4: 172.20.20.34

Five telemetry containers - Prometheus, gNMIc, Grafana, Loki, Promtail - each with its generated config file mounted as a read-only bind.

Eight links connecting them in the diamond pattern:

links:
  - endpoints: ["srl1:e1-1", "srl2:e1-1"]   # Path A - upper
  - endpoints: ["srl1:e1-2", "srl3:e1-1"]   # Path B - upper
  - endpoints: ["srl2:e1-2", "srl4:e1-1"]   # Path A - lower
  - endpoints: ["srl3:e1-2", "srl4:e1-2"]   # Path B - lower
  - endpoints: ["client-l2-1:eth1", "srl1:e1-3"]
  - endpoints: ["client-l2-2:eth1", "srl4:e1-3"]
  - endpoints: ["client-l3-1:eth1", "srl1:e1-4"]
  - endpoints: ["client-l3-2:eth1", "srl4:e1-4"]

The diamond has two paths. Path A runs through srl2. Path B runs through srl3. This is the topology the AI layer will eventually manipulate - shifting traffic between paths based on latency forecasts.

The entire network is air-gapped. external-access: false. No routes leave the lab. Nothing reaches the internet. The lab lives entirely within itself.


The Node Type

Every SR Linux node in this lab uses type: ixr-d2l.

This is worth calling out because the documentation contains a common error - the letter is lowercase L, not the number 1. ixr-d2l is the IXR-D2L fixed-form-factor chassis. ixr-d21 does not exist. On a laptop, the D2L is the stable choice - it initializes correctly without requiring modular chassis configuration.

Getting this wrong produces containers that start and immediately exit. Getting it right produces four routers that boot cleanly and hold their configuration.


What Happened When It Ran

ContainerLab printed its progress as it worked. First the image pulls for containers not yet present locally. Then container creation - all thirteen, nearly simultaneously. Then the virtual link connections:

INFO Created link: srl1:e1-1 ▪┄┄▪ srl2:e1-1
INFO Created link: srl1:e1-2 ▪┄┄▪ srl3:e1-1
INFO Created link: srl2:e1-2 ▪┄┄▪ srl4:e1-1
INFO Created link: srl3:e1-2 ▪┄┄▪ srl4:e1-2
INFO Created link: client-l2-1:eth1 ▪┄┄▪ srl1:e1-3
INFO Created link: client-l2-2:eth1 ▪┄┄▪ srl4:e1-3
INFO Created link: client-l3-1:eth1 ▪┄┄▪ srl1:e1-4
INFO Created link: client-l3-2:eth1 ▪┄┄▪ srl4:e1-4

Then the summary table. Thirteen rows. Every row: running.

The diamond fabric exists.


What the Startup Config Does

Each SR Linux node loads a startup configuration file at boot. This is the "birth certificate" approach - the node comes up already configured rather than requiring manual CLI input after the fact.

If I were configuring srl1 by hand, which I often do in live networks, I would enter the CLI, type every line, and commit the changes.

It would look like this:

--{ candidate }--[ ]--
A:srl1# set / interface system0 subinterface 0 ipv4 admin-state enable
A:srl1# set / interface system0 subinterface 0 ipv4 address 172.1.255.255/32
A:srl1# set / interface ethernet-1/1 admin-state enable
A:srl1# set / interface ethernet-1/1 subinterface 0 ipv4 admin-state enable
A:srl1# set / interface ethernet-1/1 subinterface 0 ipv4 address 172.254.254.1/30
A:srl1# set / interface ethernet-1/2 admin-state enable
A:srl1# set / interface ethernet-1/2 subinterface 0 ipv4 admin-state enable
A:srl1# set / interface ethernet-1/2 subinterface 0 ipv4 address 172.254.254.5/30
A:srl1# set / network-instance default admin-state enable
A:srl1# set / network-instance default interface system0.0
A:srl1# set / network-instance default interface ethernet-1/1.0
A:srl1# set / network-instance default interface ethernet-1/2.0
A:srl1# commit stay

That is just the interface configuration for one node. Before IS-IS. Before BGP. Before services. Before telemetry.

You would type that four times - once per node - and each node would have different IP addresses, a different system IP, different neighbor relationships. The next installment covers IS-IS, and you will see exactly what four nodes of manual routing protocol configuration looks like.

The startup config file handles all of that automatically at boot. The Python script generates it. The IPs derive from the site ID - srl1 is site 1, so its system IP is 172.1.255.255. srl2 is site 2, so its system IP is 172.2.255.255. The pattern is consistent and never hardcoded.


What Comes Next

ContainerLab reports the containers as running. But running in Docker terms means the container process started. It does not mean the Nokia SR Linux operating system inside that container has finished initializing.

SR Linux takes two to four minutes after Docker reports running before the NOS is ready to accept CLI connections, process startup configs, and form protocol adjacencies. This is not a bug. It is a carrier-grade operating system doing what carrier-grade operating systems do - initializing carefully.

The next installment starts after that wait is over. IS-IS adjacencies forming. The network learning the shape of the room it is in.


Build Record

Deployed May 17, 2026 - Dell Precision 3571 - Garuda Linux rolling - ContainerLab 0.71.0 - SR Linux v26.3.2

10:13:09 INFO Containerlab started version=0.71.0
10:13:09 INFO Parsing & checking topology file=lab.yml
10:13:09 INFO Creating docker network name=clab-nbl-diamond-v1-mgmt
             IPv4 subnet=172.20.20.0/24
10:13:40 INFO Creating container name=srl1
10:13:40 INFO Creating container name=srl2
10:13:40 INFO Creating container name=srl3
10:13:40 INFO Creating container name=srl4
10:13:43 INFO Created link: srl1:e1-1 ▪┄┄▪ srl2:e1-1
10:13:43 INFO Created link: srl1:e1-2 ▪┄┄▪ srl3:e1-1
10:13:43 INFO Created link: srl2:e1-2 ▪┄┄▪ srl4:e1-1
10:13:43 INFO Created link: srl3:e1-2 ▪┄┄▪ srl4:e1-2

╭──────────────────────────────────┬──────────────────┬─────────┬──────────────╮
│ clab-nbl-diamond-v1-srl1         │ nokia_srlinux    │ running │ 172.20.20.11 │
│ clab-nbl-diamond-v1-srl2         │ nokia_srlinux    │ running │ 172.20.20.12 │
│ clab-nbl-diamond-v1-srl3         │ nokia_srlinux    │ running │ 172.20.20.13 │
│ clab-nbl-diamond-v1-srl4         │ nokia_srlinux    │ running │ 172.20.20.14 │
│ clab-nbl-diamond-v1-client-l2-1  │ linux            │ running │ 172.20.20.31 │
│ clab-nbl-diamond-v1-client-l2-2  │ linux            │ running │ 172.20.20.32 │
│ clab-nbl-diamond-v1-client-l3-1  │ linux            │ running │ 172.20.20.33 │
│ clab-nbl-diamond-v1-client-l3-2  │ linux            │ running │ 172.20.20.34 │
│ clab-nbl-diamond-v1-prometheus   │ linux            │ running │ 172.20.20.20 │
│ clab-nbl-diamond-v1-gnmic        │ linux            │ running │ 172.20.20.21 │
│ clab-nbl-diamond-v1-grafana      │ linux            │ running │ 172.20.20.22 │
│ clab-nbl-diamond-v1-loki         │ linux            │ running │ 172.20.20.23 │
│ clab-nbl-diamond-v1-promtail     │ linux            │ running │ 172.20.20.24 │
╰──────────────────────────────────┴──────────────────┴─────────┴──────────────╯

Phase 3 Complete -- lab deployed
Nodes: 13 | Links: 8 | Bridge: br-nbl-v1 | Air-gapped: True

Thirteen containers. Eight links. Diamond fabric online.


Next installment: The Underlay. IS-IS adjacencies form. The network learns its own topology. Four nodes of manual configuration shown in full.