Up until now, our pipeline has focused on the management and mapping of data. We modeled our nodes inside our NetBox source of truth (Part 1), and then we built a dynamic handshake to pull that data straight into Ansible (Part 2).
But there’s a missing link: Where are the actual routers coming from?
If we have to manually spin up heavy virtual machines, map virtual interfaces, and configure bridge links every time we want to test our playbooks, we lose all the speed advantages of automation.
In Part 3, we treat our entire network topology exactly like software application code. We use Containerlab to orchestrate, spin up, and destroy our multi-vendor lab fabric in seconds using a single, version-controlled YAML configuration file.
The Big Picture: Expanding the Architecture
Before writing the configuration files, let’s look at how our architecture shifts when we bring Containerlab into the mix. Containerlab runs directly on your Linux host or virtual machine, utilizing lightweight containerized routing images (like FRR, VyOS, SRSIM) instead of resource-heavy hypervisor VMs.
Here is the updated execution roadmap we will build out across this guide:
Why Containerlab for Labbing?
If you’ve used tools like GNS3, you know they are great graphical sandboxes, but they have distinct drawbacks for automation pipelines:
Resource Constraints: Heavy QEMU/KVM virtual machines consume significant amounts of RAM and CPU cycles, quickly overwhelming standard laptop setups.
Lack of Version Control: You cannot easily save your entire canvas topology, interface connections, and base configurations into a Git repository.
No Automation API Hook: Starting, stopping, or replicating an environment requires manual intervention in a GUI.
Containerlab changes this by launching routers as container processes. They boot up almost instantly, consume a fraction of the memory, and use simple text files to map interfaces.
1. Setting Up the Containerlab Topology File
To define our network, we create a core topology spec file. This tells Containerlab which container images to use, how much resource overhead to allocate, and exactly how the interfaces bind to one another.
Create a file named vpls.clab.yml:
name: netdevops-lab
topology:
kinds:
nokia_srsim:
# Point to your local container registry or image path
image: localhost/nokia/srsim:25.7.R1
license: /license/license.txt
nodes:
PE1:
kind: nokia_srsim
type: sr-1 # Simulates a hardware type profile
mgmt-ipv4: 172.20.20.11
PE2:
kind: nokia_srsim
type: sr-1
mgmt-ipv4: 172.20.20.12
links:
- endpoints: ["PE1:1/1/c1/1", "PE2:1/1/c1/1"]
Note on Node Images: Nokia SRSim images and their corresponding license files are proprietary assets obtained directly via official channels. However, because Containerlab utilizes standard Infrastructure-as-Code definitions, you can easily substitute these kinds with alternative open or vendor-specific virtual images (like Cisco IOS-XRd or Arista cEOS) to match what you have available in your local environment.
Deconstructing the Configuration:
kinds: This defines global properties for specific operating systems. Instead of repeating license paths and image versions for every router, we define a template for allnokia_srsimnodes.nodes: This is where we declare our actual host targets. Notice we are assigning fixed, predictable management IP addresses (mgmt-ipv4) and modeling ansr-1profile. This ensures that our NetBox inventory data models will match our live lab environment perfectly.links: Instead of dragging lines on a visual canvas, we define links as an array matching endpoints. Containerlab automatically maps these directly into the specialized Nokia port syntax (1/1/c1/1), spawning virtual ethernet pairs (veth) on the underlying Linux kernel to splice them into the container interfaces.
2. Deploying the Fabric via CLI
Once your topology file is defined, launching your entire network infrastructure requires a single command in your Linux terminal:
sudo containerlab deploy -t vpls.clab.yml
Expected Deployment Output:
Containerlab will pull the images, construct the bridge links, start the container instances, and print a clean matrix showing your running nodes:
| Name | Kind/Image | State | IPv4/6 Address |
|---|---|---|---|
| clab-netdevops-lab-PE1 | nokia_srsim localhost/nokia/srsim:25.7.R1 | running | 172.20.20.11 3fff:172:20:20::2 |
| clab-netdevops-lab-PE2 | nokia_srsim localhost/nokia/srsim:25.7.R1 | running | 172.20.20.12 3fff:172:20:20::3 |
To tear down the entire infrastructure and free up system resources instantly when you’re done labbing:
sudo containerlab destroy -t vpls.clab.yml
3. Connecting Containerlab to our NetBox Pipeline
This is where our three-part pipeline ties together cleanly into a single workflow engine.
Aligning the Source of Truth: Preparing NetBox for PE2
Before running our playbooks, we need to ensure our single source of truth accurately reflects our expanded lab footprint. Since we only modeled our initial node (PE1) back in Part 1, we need to mirror those steps for our new peer node so that Ansible can discover it.
Log into your NetBox GUI instance and quickly add the second node:
- Add Device: Name:
PE2| Device Role:Edge Router| Site:Lab-Home| Device Type:Nokia 7750 SR-1 - Add Interface: Create interface
1/1/c1/1onPE2to match our physical fabric layout. - Assign Management IP: Allocate
172.20.20.12/24and assign it explicitly as the primary management IPv4 address forPE2.
Running the End-to-End Verification
Because we configured fixed management IPs in our vpls.clab.yml matching our records inside NetBox, our dynamic Ansible inventory infrastructure from Part 2 will now scale automatically without modifying a single line of local host files.
Let’s test the entire integrated pipeline path. With your Containerlab fabric deployed, run your dynamic inventory graph to verify connection states:
ansible-inventory -i netbox_inv.yml --graph
Your terminal output will now dynamically discover both nodes directly from the API:
@all:
|--@ungrouped:
|--@sites_lab-home:
| |--PE1
| |--PE2
|--@device_roles_edge-router:
| |--PE1
| |--PE2
|--@platforms_nokia_sros:
| |--PE1
| |--PE2
Then, execute your Nokia fact playbook to poll the live containerized nodes simultaneously:
ansible-playbook -i netbox_inv.yml nokia_info.yml
Ansible will query NetBox via API, discover that PE1 is located at 172.20.20.11 and PE2 is at 172.20.20.12, open up parallel execution pipelines straight into the containerized Nokia instances running on your laptop, and retrieve live operational states without a single static inventory file ever being created.
Conclusion & Next Steps
We have successfully migrated out of the legacy network configuration and built a modular, automated DevOps workspace:
NetBox holds our intended architectural state (Our Single Source of Truth).
Containerlab handles our physical fabric orchestration on demand (Our Infrastructure-as-Code).
Ansible glues them together, pulling configuration parameters from the source data and applying execution parameters to the target container instances.
This loop provides the foundation for advanced workflows like continuous configuration compliance, automated pre-change testing, and real-time validation.
What’s Next: Decoupling Logic with Jinja2 and YANG
Now that we have our core framework established, the next major hurdle is scalability. If you hardcode configuration blocks directly into your Ansible tasks, your playbooks quickly become unmanageable monolithic files.
In Part 4, we are going to dive into keeping our playbooks clean by completely separating our execution logic from our configuration data. We will cover:
- Jinja2 Templating: How to build dynamic configuration templates for our Nokia routers that read variables dynamically on the fly.
- Abstracting with YANG Models: Understanding how structural data models (like OpenConfig or native Nokia YANG) allow us to cleanly map network states.
- Structuring Variable Scopes: Moving variables cleanly between host files, group vars, and our NetBox API returns so that the playbook’s only job is to point to a template and push the result.