Skip to content

Module Containerlab

sat edited this page Aug 16, 2026 · 14 revisions

Module: Containerlab

This page describes the Containerlab module for dot2net.

What is Containerlab?

Containerlab is a container-based networking lab platform that enables rapid deployment of network topologies using Docker containers. It supports various network operating systems (Nokia SR Linux, Arista cEOS, FRR, etc.) and provides a simple YAML-based topology definition.

What dot2net Generates

The Containerlab module generates topo.yaml, the topology definition file for Containerlab:

name: mylab
topology:
  nodes:
    r1:
      kind: linux
      image: quay.io/frrouting/frr:8.5.4
      network-mode: none
      binds:
      - r1/etc/frr/frr.conf:/etc/frr/frr.conf
      - r1/etc/frr/daemons:/etc/frr/daemons
      exec:
      - vtysh -b
  links:
  - endpoints: ["r1:eth0", "r2:eth0"]

Generated Sections

Section Source Description
name YAML name field Lab name
nodes DOT nodes Node definitions with kind, image, binds, exec
links DOT edges Network connections between nodes

Automatic Features

  • binds: Automatically generated from FileDefinitions that have path set
  • exec: Generated from user-defined startup config block
  • links: Generated from DOT edge definitions

No management network by default

Generated nodes carry network-mode: none, so a lab has only the links its topology describes. containerlab's management network is convenient — clab exec, the clab-* names — but it is also a second path between every pair of nodes, and a reachability test that should have failed can pass through it without anyone noticing. TiNET runs its nodes with --net none and Kathara gives them no management network either, so this also brings the three into line.

module_config:
  containerlab:
    management_network: true   # put it back

Turning it back on means naming the data interfaces something other than eth0, which containerlab keeps for the management interface. dot2net says so while generating rather than letting containerlab refuse at deploy time.

Bridges the topology names

containerlab refuses to deploy while a bridge its topology names does not exist. A topology that uses a shared medium provided by the platform can pull in a ready-made class, and the module writes the script that makes them:

nodeclass:
  - name: platform_sw
    deploy: platform
    use: [clabOvsBridgeSetup]   # or clabLinuxBridgeSetup
    values:
      kind: ovs-bridge

The entry script then makes the bridges on deploy and removes them on destroy, so a lab that needs bridges comes up from the generated files with nothing done by hand. Running containerlab deploy yourself skips that step, and containerlab then refuses to deploy — its check for a named bridge runs before any stage, so nothing inside the lab can make one.

The classes write this into worker_deploy and worker_destroy, the blocks any topology can use to run commands on the machine — see Machine-side hooks. A lab whose bridges are provisioned some other way — Ansible, sudo rules, a different OVS database — leaves the use: line out and writes its own worker_deploy instead, or nothing at all.

One topology file per machine

When a topology declares worker groups, the topology file becomes group-scoped: each machine gets its own, holding its nodes and the links it can wire itself. A link that leaves a machine appears in neither, since no topology file can make it — see Placing nodes on machines.

Bind paths are stated from the machine's directory, because containerlab resolves a relative bind against the directory holding the topology file.

Required Parameters

Each node deployed as a container must have both. A node with deploy: platform — a bridge, a shared medium the platform provides rather than runs — needs only kind: nothing is deployed for it, so there is no image to name.

Parameter Description Example
image Container image quay.io/frrouting/frr:8.5.4
kind Containerlab node kind linux, srl, ceos

Define these in your NodeClass:

nodeclass:
  - name: router
    interface_policy: [p2p]   # addresses for this node's interfaces
    params: [lo]              # and ip_loopback for the node itself
    values:
      image: quay.io/frrouting/frr:8.5.4
      kind: linux

Optional: startup Config Block

The startup config block defines commands to run inside the container. This is used to generate the exec section in topo.yaml.

nodeclass:
  - name: router
    config:
      - name: startup
        template:
          - "vtysh -b"
          - "ip addr add {{ .ip_loopback }}/32 dev lo"

Behavior: a topology must define a startup template somewhere — containerlab reports node config templates named startup is required without one. A node whose startup comes out empty gets no exec: section, which is where this differs from TiNET: it writes a node_configs entry either way.

Optional: File Bind Mounts

Files with path defined in FileDefinition are automatically mounted into containers:

file:
  - name: frr.conf
    path: /etc/frr/frr.conf    # This triggers bind mount generation
  - name: daemons
    path: /etc/frr/daemons

Generated binds:

binds:
  - r1/etc/frr/frr.conf:/etc/frr/frr.conf
  - r1/etc/frr/daemons:/etc/frr/daemons

Complete Example

input.yaml

name: ospf_lab
module:
  - containerlab
  - frr

file:
  - name: frr.conf
    path: /etc/frr/frr.conf
  - name: daemons
    path: /etc/frr/daemons

layer:
  - name: ip
    default_connect: true
    policy:
      - name: p2p
        range: 10.0.0.0/16
        prefix: 30
      - name: lo
        type: loopback
        range: 10.255.0.0/24

nodeclass:
  - name: router
    interface_policy: [p2p]   # addresses for this node's interfaces
    params: [lo]              # and ip_loopback for the node itself
    values:
      image: quay.io/frrouting/frr:8.5.4
      kind: linux
    config:
      - file: frr.conf
        template:
          - "hostname {{ .name }}"
          - "!"
          - "router ospf"
          - " router-id {{ .ip_loopback }}"
      - file: daemons
        sourcefile: ./daemons
      - name: startup
        template:
          - "vtysh -b"

input.dot

graph  {
  r1 [class="router"]
  r2 [class="router"]
  r1 -- r2
}

Generated topo.yaml

name: ospf_lab
topology:
  nodes:
    r1:
      kind: linux
      image: quay.io/frrouting/frr:8.5.4
      network-mode: none
      binds:
      - r1/etc/frr/frr.conf:/etc/frr/frr.conf
      - r1/etc/frr/daemons:/etc/frr/daemons
      exec:
      - vtysh -b
    r2:
      kind: linux
      image: quay.io/frrouting/frr:8.5.4
      network-mode: none
      binds:
      - r2/etc/frr/frr.conf:/etc/frr/frr.conf
      - r2/etc/frr/daemons:/etc/frr/daemons
      exec:
      - vtysh -b

  links:
  - endpoints: [r1:eth0, r2:eth0]

Running the Lab

dot2net build -c input.yaml input.dot

sudo containerlab deploy -t topo.yaml
sudo containerlab destroy -t topo.yaml --cleanup

With an entry script

module_config.containerlab.generate_scripts: true (off by default) writes containerlab.sh beside the generated topo.yaml:

sudo ./containerlab.sh deploy
sudo ./containerlab.sh exec r1 vtysh -c "show ip ospf neighbor"
sudo ./containerlab.sh destroy

Its destroy does more than containerlab's own: it runs the lab's teardown commands, copies out the files it collects, destroys the lab, and runs the lab's worker_destroy commands — reporting any step that failed. A topology that collects files or has teardown commands needs the script for them to happen at all. See Command Reference.

See Also

Clone this wiki locally